Files
aya/client/common/shaders/bgfx_source/smoothwater.sc
2025-12-17 16:47:48 +00:00

287 lines
8.4 KiB
Scala

$input a_position, a_normal, a_texcoord0, a_texcoord1
$output v_pos, v_color0, v_color1, v_texcoord0, v_texcoord1, v_texcoord2, v_lightpos_fog, v_normal, v_view_depth, v_poslightspace
#include "common.sh"
#include "globals.sh"
// Tunables
#define CFG_TEXTURE_TILING 0.2
#define CFG_TEXTURE_DETILING 0.1
#define CFG_SPECULAR 2.0
#define CFG_GLOSS 900.0
#define CFG_NORMAL_STRENGTH 0.25
#define CFG_REFRACTION_STRENGTH 0.05
#define CFG_FRESNEL_OFFSET 0.3
#define CFG_SSR_STEPS 8
#define CFG_SSR_START_DISTANCE 1.0
#define CFG_SSR_STEP_CLAMP 0.2
#define CFG_SSR_DEPTH_CUTOFF 10.0
uniform vec4 u_waveParams; // .x = frequency .y = phase .z = height .w = lerp
uniform vec4 u_waterColor; // deep water color
uniform vec4 u_waterParams; // .x = refraction depth scale, .y = refraction depth offset
SAMPLER2D(s_normalMap1, 0);
SAMPLER2D(s_normalMap2, 1);
SAMPLERCUBE(s_envMap, 2);
SAMPLER2D(s_lightMap, 3);
SAMPLER2D(s_lightMapLookup, 4);
SAMPLER2D(s_gbufferColor, 5);
SAMPLER2D(s_gbufferDepth, 6);
vec3 displacePosition(vec3 position, float waveFactor)
{
float x = sin((position.z - position.x) * u_waveParams.x - u_waveParams.y);
float z = sin((position.z + position.x) * u_waveParams.x + u_waveParams.y);
float p = (x + z) * u_waveParams.z;
vec3 result = position;
result.y += p * waveFactor;
return result;
}
vec4 clipToScreen(vec4 pos)
{
#ifdef GLSL
pos.xy = pos.xy * 0.5 + 0.5 * pos.w;
#else
pos.xy = pos.xy * vec2(0.5, -0.5) + 0.5 * pos.w;
#endif
return pos;
}
vec2 getUV(vec3 position, int projection, float seed)
{
vec3 u = u_worldMatrixArray[1 + projection].xyz;
vec3 v = u_worldMatrixArray[19 + projection].xyz;
vec2 uv = vec2(dot(position, u), dot(position, v)) * (0.25 * CFG_TEXTURE_TILING) + CFG_TEXTURE_DETILING * vec2(seed, floor(seed * 2.6651441));
return uv;
}
void WaterVS()
{
vec3 posWorld = a_position.xyz * u_worldMatrixArray[0].w + u_worldMatrixArray[0].xyz;
vec3 normalWorld = a_normal.xyz * (1.0 / 127.0) - 1.0;
#if defined(GLSLES) && !defined(GL3) // iPad2 workaround
vec3 weights = vec3(
abs(a_position.w - 0.0) < 0.1 ? 1.0 : 0.0,
abs(a_position.w - 1.0) < 0.1 ? 1.0 : 0.0,
abs(a_position.w - 2.0) < 0.1 ? 1.0 : 0.0
);
#else
vec3 weights = vec3(
a_position.w == 0.0 ? 1.0 : 0.0,
a_position.w == 1.0 ? 1.0 : 0.0,
a_position.w == 2.0 ? 1.0 : 0.0
);
#endif
float waveFactor = dot(weights, a_texcoord0.xyz) * (1.0 / 255.0);
#ifdef PIN_HQ
float fade = saturate0(1.0 - dot(posWorld - u_cameraPosition, -u_viewDir.xyz) * u_fadeDistance_GlowFactor.y);
posWorld = displacePosition(posWorld, waveFactor * fade);
#endif
v_pos = mul(u_viewProjection, vec4(posWorld, 1.0));
v_lightpos_fog = vec4(lgridPrepareSample(lgridOffset(posWorld, normalWorld)), (u_fogParams.z - v_pos.w) * u_fogParams.w);
v_texcoord0.xy = getUV(posWorld, int(a_texcoord1.x), a_normal.w / 255.0);
v_texcoord1.xy = getUV(posWorld, int(a_texcoord1.y), a_texcoord0.w / 255.0);
v_texcoord2.xy = getUV(posWorld, int(a_texcoord1.z), a_texcoord1.w / 255.0);
v_color0.xyz = weights;
v_color0.w = waveFactor;
v_normal = normalWorld;
v_view_depth = vec4(u_cameraPosition - posWorld, v_pos.w);
v_color1.xyz = vec3(
a_texcoord1.x > 7.5 ? 1.0 : 0.0,
a_texcoord1.y > 7.5 ? 1.0 : 0.0,
a_texcoord1.z > 7.5 ? 1.0 : 0.0
); // side vs top
#ifdef PIN_HQ
v_poslightspace = clipToScreen(v_pos).xyz;
#endif
gl_Position = v_pos;
}
float fresnel(float ndotv)
{
return saturate0(0.78 - 2.5 * abs(ndotv)) + CFG_FRESNEL_OFFSET;
}
vec4 sampleMix(vec2 uv)
{
#ifdef PIN_HQ
return mix(texture2D(s_normalMap1, uv), texture2D(s_normalMap2, uv), u_waveParams.w);
#else
return texture2D(s_normalMap1, uv);
#endif
}
vec3 sampleNormal(vec2 uv0, vec2 uv1, vec2 uv2, vec3 w, vec3 normal, vec3 tsel)
{
return terrainNormal(sampleMix(uv0), sampleMix(uv1), sampleMix(uv2), w, normal, tsel);
}
vec3 sampleNormalSimple(vec2 uv0, vec2 uv1, vec2 uv2, vec3 w)
{
vec4 data = sampleMix(uv0) * w.x + sampleMix(uv1) * w.y + sampleMix(uv2) * w.z;
return nmapUnpack(data).xzy;
}
float unpackDepth(vec2 uv)
{
vec4 geomTex = texture2D(s_gbufferDepth, uv);
float d = geomTex.z * (1.0 / 256.0) + geomTex.w;
return d * GBUFFER_MAX_DEPTH;
}
vec3 getRefractedColor(vec4 cpos, vec3 N, vec3 waterColor)
{
vec2 refruv0 = cpos.xy / cpos.w;
vec2 refruv1 = refruv0 + N.xz * CFG_REFRACTION_STRENGTH;
vec4 refr0 = texture2D(s_gbufferColor, refruv0);
refr0.w = unpackDepth(refruv0);
vec4 refr1 = texture2D(s_gbufferColor, refruv1);
refr1.w = unpackDepth(refruv1);
vec4 result = mix(refr0, refr1, saturate0(refr1.w - cpos.w));
// Estimate water absorption by a scaled depth difference
float depthfade = saturate0((result.w - cpos.w) * u_waterParams.x + u_waterParams.y);
// Since GBuffer depth is clamped we tone the refraction down after half of the range for a smooth fadeout
float gbuffade = saturate0(cpos.w * (2.0 / GBUFFER_MAX_DEPTH) - 1.0);
float fade = saturate0(depthfade + gbuffade);
return mix(result.rgb, waterColor, fade);
}
vec3 getReflectedColor(vec4 cpos, vec3 wpos, vec3 R)
{
vec3 result = vec3(0.0, 0.0, 0.0);
float inside = 0.0;
float distance = CFG_SSR_START_DISTANCE;
float diff = 0.0;
float diffclamp = cpos.w * CFG_SSR_STEP_CLAMP;
vec4 Pproj = cpos;
vec4 Rproj = clipToScreen(mul(u_viewProjection, vec4(R, 0.0)));
for (int i = 0; i < CFG_SSR_STEPS; ++i)
{
distance += clamp(diff, -diffclamp, diffclamp);
vec4 cposi = Pproj + Rproj * distance;
vec2 uv = cposi.xy / cposi.w;
float depth = unpackDepth(uv);
diff = depth - cposi.w;
}
vec4 cposi = Pproj + Rproj * distance;
vec2 uv = cposi.xy / cposi.w;
// Ray hit has to be inside the screen bounds
float ufade = abs(uv.x - 0.5) < 0.5 ? 1.0 : 0.0;
float vfade = abs(uv.y - 0.5) < 0.5 ? 1.0 : 0.0;
// Fade reflections out with distance
float wfade = saturate0((4.0 - 0.1) - max(cpos.w, cposi.w) * (4.0 / GBUFFER_MAX_DEPTH));
// Ray hit has to be reasonably close to where we started
float dfade = abs(diff) < CFG_SSR_DEPTH_CUTOFF ? 1.0 : 0.0;
// Avoid back-projection
float Vfade = Rproj.w > 0.0 ? 1.0 : 0.0;
float fade = ufade * vfade * wfade * dfade * Vfade;
return mix(textureCube(s_envMap, R).rgb, texture2D(s_gbufferColor, uv).rgb, fade);
}
void WaterPS()
{
vec4 light = lgridSample(s_lightMap, s_lightMapLookup, v_lightpos_fog.xyz);
float shadow = light.a;
vec3 w = v_color0.xyz;
// Use simplified normal reconstruction for LQ mobile (assumes flat water surface)
#if defined(GLSLES) && !defined(PIN_HQ)
vec3 normal = sampleNormalSimple(v_texcoord0.xy, v_texcoord1.xy, v_texcoord2.xy, w);
#else
vec3 normal = sampleNormal(v_texcoord0.xy, v_texcoord1.xy, v_texcoord2.xy, w, v_normal, v_color1.xyz);
#endif
// Flatten the normal for Fresnel and for reflections to make them less chaotic
vec3 flatNormal = mix(v_normal, normal, CFG_NORMAL_STRENGTH);
vec3 waterColor = u_waterColor.rgb;
#ifdef PIN_HQ
float fade = saturate0(1.0 - v_view_depth.w * u_fadeDistance_GlowFactor.y);
vec3 view = normalize(v_view_depth.xyz);
float fre = fresnel(dot(flatNormal, view)) * v_color0.w;
vec3 position = u_cameraPosition - v_view_depth.xyz;
#ifdef PIN_GBUFFER
vec3 refr = getRefractedColor(vec4(v_poslightspace, v_view_depth.w), normal, waterColor);
vec3 refl = getReflectedColor(vec4(v_poslightspace, v_view_depth.w), position, reflect(-view, flatNormal));
#else
vec3 refr = waterColor;
vec3 refl = textureCube(s_envMap, reflect(-view, flatNormal)).rgb;
#endif
float specularIntensity = CFG_SPECULAR * fade;
float specularPower = CFG_GLOSS;
vec3 specular = u_lamp0Color * (specularIntensity * shadow * pow(saturate0(dot(normal, normalize(-u_lamp0Dir + view))), specularPower));
#else
vec3 view = normalize(v_view_depth.xyz);
float fre = fresnel(dot(flatNormal, view));
vec3 refr = waterColor;
vec3 refl = textureCube(s_envMap, reflect(-v_view_depth.xyz, flatNormal)).rgb;
vec3 specular = vec3(0.0, 0.0, 0.0);
#endif
// Combine
vec4 result;
result.rgb = mix(refr, refl, fre) * (u_ambientColor.rgb + u_lamp0Color.rgb * shadow + light.rgb) + specular;
result.a = 1.0;
float fogAlpha = saturate0(v_lightpos_fog.w);
result.rgb = mix(u_fogColor, result.rgb, fogAlpha);
gl_FragColor = result;
}