From dda21aeae1249eae009eb34d60853532e0b0ed96 Mon Sep 17 00:00:00 2001 From: Stuart Buchanan Date: Sat, 24 Feb 2024 17:09:51 +0000 Subject: [PATCH] HDR: W3.0 water shader - initial implementation Implement a simple water shader for HDR. Largely created from the non-HDR WS3.0 water shader, but only generating a fragment normal. Notes: - Water color is a constant in the shader, and set by eye only. - foam etc is not yet implemented. - at very low altitudes the shader breaks down somewhat. --- Effects/ws30.eff | 62 +++++++++++ Shaders/HDR/water.glsl | 247 +++++++++++++++++++++++++++++++++++++++++ Shaders/HDR/ws30.frag | 26 ++++- 3 files changed, 330 insertions(+), 5 deletions(-) create mode 100644 Shaders/HDR/water.glsl diff --git a/Effects/ws30.eff b/Effects/ws30.eff index 4a5ae4e7b..235110f73 100644 --- a/Effects/ws30.eff +++ b/Effects/ws30.eff @@ -1932,6 +1932,7 @@ Shaders/HDR/ws30.vert Shaders/HDR/logarithmic_depth.glsl Shaders/HDR/ws30.frag + Shaders/HDR/water.glsl Shaders/HDR/logarithmic_depth.glsl Shaders/HDR/gbuffer_pack.glsl Shaders/HDR/normal_encoding.glsl @@ -1953,6 +1954,67 @@ sampler-2d 6 + + coastline + sampler-2d + 7 + + + WindE + float + + windE + + + + WindN + float + + windN + + + + WaveFreq + float + + WaveFreq + + + + WaveAmp + float + + WaveAmp + + + + WaveSharp + float + + WaveSharp + + + + WaveAngle + float + + WaveAngle + + + + WaveFactor + float + + WaveFactor + + + + WaveDAngle + float + + WaveDAngle + + diff --git a/Shaders/HDR/water.glsl b/Shaders/HDR/water.glsl new file mode 100644 index 000000000..d0866f0bf --- /dev/null +++ b/Shaders/HDR/water.glsl @@ -0,0 +1,247 @@ +// SPDX-FileCopyrightText: (C) 2024 Stuart Buchanan stuart13@gmail.com +// SPDX-License-Identifier: GPL-2.0-or-later + +// Helper functions for WS30 HDR water implementation + +#version 330 core + +// Hardcoded indexes into the texture atlas +const int ATLAS_INDEX_WATER = 0; +const int ATLAS_INDEX_WATER_REFLECTION = 1; +const int ATLAS_INDEX_WAVES_VERT10_NM = 2; +const int ATLAS_INDEX_WATER_SINE_NMAP = 3; +const int ATLAS_INDEX_WATER_REFLECTION_GREY = 4; +const int ATLAS_INDEX_SEA_FOAM = 5; +const int ATLAS_INDEX_PERLIN_NOISE_NM = 6; +const int ATLAS_INDEX_OCEAN_DEPTH = 7; +const int ATLAS_INDEX_GLOBAL_COLORS = 8; +const int ATLAS_INDEX_PACKICE_OVERLAY = 9; + +// WS30 uniforms +uniform sampler2DArray atlas; +uniform float fg_tileWidth; +uniform float fg_tileHeight; + +// Water.eff uniforms +uniform float osg_SimulationTime; +uniform float WindN; +uniform float WindE; +uniform float WaveFreq; +uniform float WaveAmp; +uniform float WaveSharp; +uniform float WaveAngle; +uniform float WaveFactor; + +/////// functions ///////// + +void rotationmatrix(in float angle, out mat4 rotmat) +{ + rotmat = mat4( cos( angle ), -sin( angle ), 0.0, 0.0, + sin( angle ), cos( angle ), 0.0, 0.0, + 0.0 , 0.0 , 1.0, 0.0, + 0.0 , 0.0 , 0.0, 1.0 ); +} + +// wave functions /////////////////////// +struct Wave { + float freq; // 2*PI / wavelength + float amp; // amplitude + float phase; // speed * 2*PI / wavelength + vec2 dir; +}; + +Wave wave0 = Wave(1.0, 1.0, 0.5, vec2(0.97, 0.25)); +Wave wave1 = Wave(2.0, 0.5, 1.3, vec2(0.97, -0.25)); +Wave wave2 = Wave(1.0, 1.0, 0.6, vec2(0.95, -0.3)); +Wave wave3 = Wave(2.0, 0.5, 1.4, vec2(0.99, 0.1)); + +float evaluateWave(in Wave w, in vec2 pos, in float t) { + return w.amp * sin( dot(w.dir, pos) * w.freq + t * w.phase); +} + +// derivative of wave function +float evaluateWaveDeriv(in Wave w, in vec2 pos, in float t) { + return w.freq * w.amp * cos( dot(w.dir, pos)*w.freq + t*w.phase); +} + +// sharp wave functions +float evaluateWaveSharp(in Wave w, in vec2 pos, in float t, in float k) { + return w.amp * pow(sin( dot(w.dir, pos)*w.freq + t*w.phase)* 0.5 + 0.5 , k); +} + +float evaluateWaveDerivSharp(in Wave w, in vec2 pos, in float t, in float k) { + return k*w.freq*w.amp * pow(sin( dot(w.dir, pos)*w.freq + t*w.phase)* 0.5 + 0.5 , k - 1) * cos( dot(w.dir, pos)*w.freq + t*w.phase); +} + +void sumWaves(in float angle, in float dangle, in float windScale, in float factor, in vec4 waterTex1, out float ddx, float ddy) { + mat4 RotationMatrix; + float deriv; + vec4 P = waterTex1 * 1024; + + rotationmatrix(radians(angle + dangle * windScale + 0.6 * sin(P.x * factor)), RotationMatrix); + P *= RotationMatrix; + + P.y += evaluateWave(wave0, P.xz, osg_SimulationTime); + deriv = evaluateWaveDeriv(wave0, P.xz, osg_SimulationTime ); + ddx = deriv * wave0.dir.x; + ddy = deriv * wave0.dir.y; + + P.y += evaluateWaveSharp(wave2, P.xz, osg_SimulationTime, WaveSharp); + deriv = evaluateWaveDerivSharp(wave2, P.xz, osg_SimulationTime, WaveSharp); + ddx += deriv * wave2.dir.x; + ddy += deriv * wave2.dir.y; + +} + +vec3 generateWaterNormal(in vec2 texCoords) +{ + float tileScale = 1 / (fg_tileHeight + fg_tileWidth) / 2.0; + + vec4 sca = vec4(0.005, 0.005, 0.005, 0.005) * tileScale; + vec4 sca2 = vec4(0.02, 0.02, 0.02, 0.02) * tileScale; + vec4 tscale = vec4(0.25, 0.25, 0.25, 0.25) / 10000.0 * tileScale; + + vec3 Normal = vec3 (0.0, 0.0, 1.0); + + const float water_shininess = 240.0; + + float windEffect = sqrt( WindE*WindE + WindN*WindN ) * 0.6; //wind speed in kt + float windScale = 15.0/(3.0 + windEffect); //wave scale + float windEffect_low = 0.3 + 0.7 * smoothstep(0.0, 5.0, windEffect); //low windspeed wave filter + float waveRoughness = 0.01 + smoothstep(0.0, 40.0, windEffect); //wave roughness filter + + vec4 t1 = vec4(0.0, osg_SimulationTime * 0.005217, 0.0, 0.0); + vec4 t2 = vec4(0.0, osg_SimulationTime * -0.0012, 0.0, 0.0); + + float Angle; + + float windFactor = sqrt(WindE * WindE + WindN * WindN) * 0.05; + if (WindN == 0.0 && WindE == 0.0) { + Angle = 0.0; + } else { + Angle = atan(-WindN, WindE) - atan(1.0); + } + + mat4 RotationMatrix; + rotationmatrix(Angle, RotationMatrix); + vec4 waterTex1 = vec4(texCoords.s, texCoords.t, 0.0, 0.0) * RotationMatrix - t1 * windFactor; + rotationmatrix(Angle, RotationMatrix); + vec4 waterTex2 = vec4(texCoords.s, texCoords.t, 0.0, 0.0) * RotationMatrix - t2 * windFactor; + + float mixFactor = 0.2 + 0.02 * smoothstep(0.0, 50.0, windEffect); + mixFactor = clamp(mixFactor, 0.3, 0.8); + + // sine waves + float ddx, ddx1, ddx2, ddx3, ddy, ddy1, ddy2, ddy3; + float angle; + + ddx = 0.0, ddy = 0.0; + ddx1 = 0.0, ddy1 = 0.0; + ddx2 = 0.0, ddy2 = 0.0; + ddx3 = 0.0, ddy3 = 0.0; + + angle = 0.0; + + wave0.freq = WaveFreq ; + wave0.amp = WaveAmp; + wave0.dir = vec2 (0.0, 1.0); //vec2(cos(radians(angle)), sin(radians(angle))); + + angle -= 45; + wave1.freq = WaveFreq * 2.0 ; + wave1.amp = WaveAmp * 1.25; + wave1.dir = vec2(0.70710, -0.7071); //vec2(cos(radians(angle)), sin(radians(angle))); + + angle += 30; + wave2.freq = WaveFreq * 3.5; + wave2.amp = WaveAmp * 0.75; + wave2.dir = vec2(0.96592, -0.2588);// vec2(cos(radians(angle)), sin(radians(angle))); + + angle -= 50; + wave3.freq = WaveFreq * 3.0 ; + wave3.amp = WaveAmp * 0.75; + wave3.dir = vec2(0.42261, -0.9063); //vec2(cos(radians(angle)), sin(radians(angle))); + + // sum waves + sumWaves(WaveAngle, -1.5, windScale, WaveFactor, waterTex1, ddx, ddy); + sumWaves(WaveAngle, 1.5, windScale, WaveFactor, waterTex1, ddx1, ddy1); + + //reset the waves + angle = 0.0; + float waveamp = WaveAmp * 0.75; + + wave0.freq = WaveFreq ; + wave0.amp = waveamp; + wave0.dir = vec2 (0.0, 1.0); //vec2(cos(radians(angle)), sin(radians(angle))); + + angle -= 20; + wave1.freq = WaveFreq * 2.0 ; + wave1.amp = waveamp * 1.25; + wave1.dir = vec2(0.93969, -0.34202);// vec2(cos(radians(angle)), sin(radians(angle))); + + angle += 35; + wave2.freq = WaveFreq * 3.5; + wave2.amp = waveamp * 0.75; + wave2.dir = vec2(0.965925, 0.25881); //vec2(cos(radians(angle)), sin(radians(angle))); + + angle -= 45; + wave3.freq = WaveFreq * 3.0 ; + wave3.amp = waveamp * 0.75; + wave3.dir = vec2(0.866025, -0.5); //vec2(cos(radians(angle)), sin(radians(angle))); + // end sine stuff + + vec2 st = vec2(waterTex2 * tscale * windScale); + vec4 disdis = texture(atlas, vec3(st, ATLAS_INDEX_WATER_SINE_NMAP)) * 2.0 - 1.0; + + //normalmaps + st = vec2(waterTex1 + disdis * sca2) * windScale; + vec4 nmap = texture(atlas, vec3(st, ATLAS_INDEX_WAVES_VERT10_NM)) * 2.0 - 1.0; + vec4 nmap1 = texture(atlas, vec3(st, ATLAS_INDEX_PERLIN_NOISE_NM)) * 2.0 - 1.0; + + rotationmatrix(radians(3.0 * sin(osg_SimulationTime * 0.0075)), RotationMatrix); + st = vec2(waterTex2 * RotationMatrix * tscale) * windScale; + nmap += texture(atlas, vec3(st, ATLAS_INDEX_WAVES_VERT10_NM)) * 2.0 - 1.0; + + nmap *= windEffect_low; + nmap1 *= windEffect_low; + + // mix water and noise, modulated by factor + vec4 vNorm = normalize(mix(nmap, nmap1, mixFactor) * waveRoughness); + vNorm.r += ddx + ddx1 + ddx2 + ddx3; + + st = vec2(waterTex1 + disdis * sca2) * windScale; + vec3 N0 = vec3(texture(atlas, vec3(st, ATLAS_INDEX_WAVES_VERT10_NM))) * 2.0 - 1.0; + st = vec2(waterTex1 + disdis * sca) * windScale; + vec3 N1 = vec3(texture(atlas, vec3(st, ATLAS_INDEX_PERLIN_NOISE_NM))) * 2.0 - 1.0; + + st = vec2(waterTex1 * tscale) * windScale; + N0 += vec3(texture(atlas, vec3(st, ATLAS_INDEX_WAVES_VERT10_NM))) * 2.0 - 1.0; + N1 += vec3(texture(atlas, vec3(st, ATLAS_INDEX_PERLIN_NOISE_NM))) * 2.0 - 1.0; + + rotationmatrix(radians(2.0 * sin(osg_SimulationTime * 0.005)), RotationMatrix); + st = vec2(waterTex2 * RotationMatrix * (tscale + sca2)) * windScale; + N0 += vec3(texture(atlas, vec3(st, ATLAS_INDEX_WAVES_VERT10_NM))) * 2.0 - 1.0; + N1 += vec3(texture(atlas, vec3(st, ATLAS_INDEX_PERLIN_NOISE_NM))) * 2.0 - 1.0; + + rotationmatrix(radians(-4.0 * sin(osg_SimulationTime * 0.003)), RotationMatrix); + st = vec2(waterTex1 * RotationMatrix + disdis * sca2) * windScale; + N0 += vec3(texture(atlas, vec3(st, ATLAS_INDEX_WAVES_VERT10_NM))) * 2.0 - 1.0; + st = vec2(waterTex1 * RotationMatrix + disdis * sca) * windScale; + N1 += vec3(texture(atlas, vec3(st, ATLAS_INDEX_PERLIN_NOISE_NM))) * 2.0 - 1.0; + + N0 *= windEffect_low; + N1 *= windEffect_low; + + N0.r += (ddx + ddx1 + ddx2 + ddx3); + N0.g += (ddy + ddy1 + ddy2 + ddy3); + + vec3 N = normalize(mix(Normal + N0, Normal + N1, mixFactor) * waveRoughness); + + // From observation, the normal is generated in the wrong direction, resulting + // in specular highlights facing away from the Sun. This matrix attempts to correct it + + mat3 rotMat = mat3(0.0, 0.0, 1.0, + 0.0, 1.0, 0.0, + 1.0, 0.0, 0.0); + N = N * rotMat; + return N; +} diff --git a/Shaders/HDR/ws30.frag b/Shaders/HDR/ws30.frag index 6f52ccfe4..5ccec1bf3 100644 --- a/Shaders/HDR/ws30.frag +++ b/Shaders/HDR/ws30.frag @@ -22,10 +22,18 @@ uniform vec4 fg_textureLookup1[128]; uniform vec4 fg_textureLookup2[128]; uniform mat4 fg_zUpTransform; uniform vec3 fg_modelOffset; +uniform sampler2D coastline; const float TERRAIN_METALLIC = 0.0; const float TERRAIN_ROUGHNESS = 0.95; +const vec3 WATER_COLOR = vec3(0.1, 0.1, 0.3); +const float WATER_METALLIC = 0.0; +const float WATER_ROUGHNESS = 0.25; + +// Procedurally generate a water normal for this fragment +vec3 generateWaterNormal(in vec2 texCoords); + // gbuffer_pack.glsl void gbuffer_pack(vec3 normal, vec3 base_color, float metallic, float roughness, float occlusion, vec3 emissive, uint mat_id); @@ -38,6 +46,9 @@ void main() { vec3 texel; vec4 specular = vec4(0.1, 0.1, 0.1, 1.0); + vec3 N = normalize(fs_in.vertex_normal); + float metallic = TERRAIN_METALLIC; + float roughness = TERRAIN_ROUGHNESS; if (fg_photoScenery) { texel = texture(landclass, vec2(fs_in.texcoord.s, 1.0 - fs_in.texcoord.t)).rgb; @@ -46,12 +57,13 @@ void main() // The Landclass for this particular fragment. This can be used to // index into the atlas textures. int lc = int(texture2D(landclass, fs_in.texcoord).g * 255.0 + 0.5); + bool water = (texture2D(landclass, fs_in.texcoord).z > 0.9) || (texture2D(coastline, fs_in.texcoord).b > 0.05); uint tex1 = uint(fg_textureLookup1[lc].r * 255.0 + 0.5); //color = ambientArray[lc] + diffuseArray[lc] * NdotL * gl_LightSource[0].diffuse; specular = fg_specularArray[lc]; - // Different textures have different have different dimensions. + // Different textures have different dimensions. vec2 atlas_dimensions = fg_dimensionsArray[lc].st; vec2 atlas_scale = vec2(fg_tileWidth / atlas_dimensions.s, fg_tileHeight / atlas_dimensions.t ); vec2 st = atlas_scale * fs_in.texcoord; @@ -66,12 +78,16 @@ void main() } texel = texture(atlas, vec3(st, tex1)).rgb; + + if (water) { + N = generateWaterNormal(fs_in.texcoord); + texel = WATER_COLOR; + roughness = WATER_ROUGHNESS; + metallic = WATER_METALLIC; + } } - vec3 color = eotf_inverse_sRGB(texel); - vec3 N = normalize(fs_in.vertex_normal); - - gbuffer_pack(N, color, TERRAIN_METALLIC, TERRAIN_ROUGHNESS, 1.0, vec3(0.0), 3u); + gbuffer_pack(N, color, metallic, roughness, 1.0, vec3(0.0), 3u); gl_FragDepth = logdepth_encode(fs_in.flogz); }