diff --git a/gl/cubemap.cpp b/gl/cubemap.cpp index 89ef9d1b..1b8d71b9 100644 --- a/gl/cubemap.cpp +++ b/gl/cubemap.cpp @@ -32,7 +32,9 @@ void gl::cubemap::bind(int unit) void gl::cubemap::generate_mipmaps() { - glBindTexture(GL_TEXTURE_CUBE_MAP, *this); - glGenerateMipmap(GL_TEXTURE_CUBE_MAP); - glBindTexture(GL_TEXTURE_CUBE_MAP, 0); + glBindTexture(GL_TEXTURE_CUBE_MAP, *this); + glGenerateMipmap(GL_TEXTURE_CUBE_MAP); + glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR); + glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + glBindTexture(GL_TEXTURE_CUBE_MAP, 0); } diff --git a/input/command.cpp b/input/command.cpp index 305b1a3d..b963905b 100644 --- a/input/command.cpp +++ b/input/command.cpp @@ -147,6 +147,7 @@ commanddescription_sequence Commands_descriptions = { {"radiostopenable", command_target::vehicle, command_mode::oneoff}, {"radiostopdisable", command_target::vehicle, command_mode::oneoff}, {"radiostoptest", command_target::vehicle, command_mode::oneoff}, + {"radiocall1send", command_target::vehicle, command_mode::oneoff}, {"radiocall3send", command_target::vehicle, command_mode::oneoff}, {"radiovolumeincrease", command_target::vehicle, command_mode::oneoff}, {"radiovolumedecrease", command_target::vehicle, command_mode::oneoff}, @@ -526,6 +527,7 @@ std::unordered_map commandMap = { {"radiostopenable", user_command::radiostopenable}, {"radiostopdisable", user_command::radiostopdisable}, {"radiostoptest", user_command::radiostoptest}, + {"radiocall1send", user_command::radiocall1send}, {"radiocall3send", user_command::radiocall3send}, {"radiovolumeincrease", user_command::radiovolumeincrease}, {"radiovolumedecrease", user_command::radiovolumedecrease}, diff --git a/input/command.h b/input/command.h index 44a25fbd..53c56d7e 100644 --- a/input/command.h +++ b/input/command.h @@ -141,6 +141,7 @@ enum class user_command radiostopenable, radiostopdisable, radiostoptest, + radiocall1send, radiocall3send, radiovolumeincrease, radiovolumedecrease, diff --git a/input/drivermouseinput.cpp b/input/drivermouseinput.cpp index b829521b..e1184b20 100644 --- a/input/drivermouseinput.cpp +++ b/input/drivermouseinput.cpp @@ -835,6 +835,9 @@ drivermouse_input::default_bindings() { { "radiotest_sw:", { user_command::radiostoptest, user_command::none } }, + { "radiocall1_sw:", { + user_command::radiocall1send, + user_command::none } }, { "radiocall3_sw:", { user_command::radiocall3send, user_command::none } }, diff --git a/rendering/opengl33renderer.cpp b/rendering/opengl33renderer.cpp index 44a0c7a5..7871cbdf 100644 --- a/rendering/opengl33renderer.cpp +++ b/rendering/opengl33renderer.cpp @@ -162,6 +162,35 @@ bool opengl33_renderer::Init(GLFWwindow *Window) m_pfx_tonemapping = std::make_unique("tonemapping"); m_pfx_chromaticaberration = std::make_unique( "chromaticaberration" ); + m_pfx_ssao = std::make_unique("ssao"); + m_pfx_ssao_blur = std::make_unique("ssao_blur"); + m_pfx_ssao_apply = std::make_unique("ssao_apply"); + + // Generate hemisphere kernel (z > 0 = toward surface normal) + std::uniform_real_distribution rnd(0.0f, 1.0f); + std::default_random_engine gen(42); // fixed seed for consistency + for (int i = 0; i < 32; i++) { + glm::vec3 s(rnd(gen)*2.0f-1.0f, rnd(gen)*2.0f-1.0f, rnd(gen)); + s = glm::normalize(s) * rnd(gen); + float scale = float(i) / 32.0f; + scale = glm::mix(0.1f, 1.0f, scale * scale); // accelerating interpolant + m_ssao_kernel[i] = s * scale; + } + + // Generate 4x4 noise texture (random rotation vectors in XY) + std::vector noise_data; + for (int i = 0; i < 16; i++) + noise_data.push_back(glm::normalize(glm::vec3(rnd(gen)*2.0f-1.0f, rnd(gen)*2.0f-1.0f, 0.0f))); + + // noise texture (4x4 random rotation vectors in tangent space) + // noise texture (4x4 random rotation vectors in tangent space) + m_ssao_noise_tex = std::make_unique(); + m_ssao_noise_tex->alloc_rendertarget(GL_RGB32F, GL_RGB, 4, 4, 1, 1, GL_REPEAT); + m_ssao_noise_tex->bind(0); + glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 4, 4, GL_RGB, GL_FLOAT, noise_data.data()); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + m_empty_cubemap = std::make_unique(); m_empty_cubemap->alloc(Global.gfx_format_color, 16, 16, GL_RGB, GL_FLOAT); @@ -426,7 +455,7 @@ bool opengl33_renderer::init_viewport(viewport_config &vp) vp.msaa_fb->attach(*vp.msaa_rbc, GL_COLOR_ATTACHMENT0); vp.msaa_fb->attach(*vp.msaa_rbd, GL_DEPTH_ATTACHMENT); - if (Global.gfx_postfx_chromaticaberration_enabled || Global.gfx_postfx_motionblur_enabled) + if (Global.gfx_postfx_chromaticaberration_enabled || Global.gfx_postfx_motionblur_enabled || Global.gfx_postfx_ssao_enabled) { vp.main_tex = std::make_unique(); vp.main_tex->alloc_rendertarget(Global.gfx_format_color, GL_RGB, vp.width, vp.height, 1, 1, GL_CLAMP_TO_EDGE); @@ -470,6 +499,25 @@ bool opengl33_renderer::init_viewport(viewport_config &vp) vp.main2_fb = std::make_unique(); vp.main2_fb->attach(*vp.main2_tex, GL_COLOR_ATTACHMENT0); + + if (Global.gfx_postfx_ssao_enabled) + { + int ssao_w = std::max(1, vp.width / 2); + int ssao_h = std::max(1, vp.height / 2); + + vp.ssao_tex = std::make_unique(); + vp.ssao_tex->alloc_rendertarget(GL_R8, GL_RED, ssao_w, ssao_h, 1, 1, GL_CLAMP_TO_EDGE); + vp.ssao_fb = std::make_unique(); + vp.ssao_fb->attach(*vp.ssao_tex, GL_COLOR_ATTACHMENT0); + if (!vp.ssao_fb->is_complete()) { ErrorLog("ssao framebuffer setup failed"); return false; } + + vp.ssao_blur_tex = std::make_unique(); + vp.ssao_blur_tex->alloc_rendertarget(GL_R8, GL_RED, ssao_w, ssao_h, 1, 1, GL_CLAMP_TO_EDGE); + vp.ssao_blur_fb = std::make_unique(); + vp.ssao_blur_fb->attach(*vp.ssao_blur_tex, GL_COLOR_ATTACHMENT0); + if (!vp.ssao_blur_fb->is_complete()) { ErrorLog("ssao blur framebuffer setup failed"); return false; } + } + if (!vp.main2_fb->is_complete()) return false; } @@ -913,7 +961,7 @@ void opengl33_renderer::Render_pass(viewport_config &vp, rendermode const Mode) if (!Global.gfx_usegles) glPolygonMode(GL_FRONT_AND_BACK, GL_FILL); - if (!Global.gfx_skippipeline) +if (!Global.gfx_skippipeline) { if (Global.gfx_postfx_motionblur_enabled) { @@ -927,12 +975,36 @@ void opengl33_renderer::Render_pass(viewport_config &vp, rendermode const Mode) model_ubo->update(model_ubs); m_pfx_motionblur->apply({vp.main_tex.get(), vp.main_texv.get(), vp.main_texd.get()}, vp.main2_fb.get()); } + else if (Global.gfx_postfx_ssao_enabled) + { + // resolve color+depth so SSAO can sample the depth texture + vp.main_fb->clear(GL_COLOR_BUFFER_BIT); + vp.main_fb->setup_drawing(1); + vp.msaa_fb->blit_to(vp.main_fb.get(), vp.width, vp.height, GL_COLOR_BUFFER_BIT, GL_COLOR_ATTACHMENT0); + vp.msaa_fb->blit_to(vp.main_fb.get(), vp.width, vp.height, GL_DEPTH_BUFFER_BIT, GL_DEPTH_ATTACHMENT); + } else { vp.main2_fb->clear(GL_COLOR_BUFFER_BIT); vp.msaa_fb->blit_to(vp.main2_fb.get(), vp.width, vp.height, GL_COLOR_BUFFER_BIT, GL_COLOR_ATTACHMENT0); } + // SSAO: depth -> occlusion -> blur -> modulate color into main2_fb + if (Global.gfx_postfx_ssao_enabled && !Global.gfx_postfx_motionblur_enabled) + { + int ssao_w = std::max(1, vp.width / 2); + int ssao_h = std::max(1, vp.height / 2); + + // (kernel is generated procedurally in the shader; no UBO upload needed) + glViewport(0, 0, ssao_w, ssao_h); + m_pfx_ssao ->apply({ vp.main_texd.get(), m_ssao_noise_tex.get() }, vp.ssao_fb.get()); + m_pfx_ssao_blur->apply({ vp.ssao_tex.get() }, vp.ssao_blur_fb.get()); + + glViewport(0, 0, vp.width, vp.height); + vp.main2_fb->clear(GL_COLOR_BUFFER_BIT); + m_pfx_ssao_apply->apply({ vp.main_tex.get(), vp.ssao_blur_tex.get() }, vp.main2_fb.get()); + } + if (!Global.gfx_usegles && !Global.gfx_shadergamma) glEnable(GL_FRAMEBUFFER_SRGB); diff --git a/rendering/opengl33renderer.h b/rendering/opengl33renderer.h index 04ead7f0..f3271154 100644 --- a/rendering/opengl33renderer.h +++ b/rendering/opengl33renderer.h @@ -227,6 +227,12 @@ class opengl33_renderer : public gfx_renderer { std::unique_ptr main2_fb; std::unique_ptr main2_tex; + // ssao + std::unique_ptr ssao_tex; + std::unique_ptr ssao_blur_tex; + std::unique_ptr ssao_fb; + std::unique_ptr ssao_blur_fb; + // LDR backbuffer for offscreen rendering std::unique_ptr backbuffer_fb; std::unique_ptr backbuffer_tex; @@ -396,6 +402,13 @@ class opengl33_renderer : public gfx_renderer { std::unique_ptr m_pfx_tonemapping; std::unique_ptr m_pfx_chromaticaberration; + // postfx ssao + std::unique_ptr m_pfx_ssao; + std::unique_ptr m_pfx_ssao_blur; + std::unique_ptr m_pfx_ssao_apply; + std::unique_ptr m_ssao_noise_tex; + glm::vec3 m_ssao_kernel[32]; + std::unique_ptr m_shadow_shader; std::unique_ptr m_alpha_shadow_shader; diff --git a/shaders/envmapping.glsl b/shaders/envmapping.glsl index 72a0ddc1..019d742b 100644 --- a/shaders/envmapping.glsl +++ b/shaders/envmapping.glsl @@ -13,3 +13,16 @@ vec3 envmap_color( vec3 normal ) #endif return envcolor; } + +// Roughness-aware env map lookup — uses mip levels for blurry reflections. +// lod 0.0 = mirror sharp, lod ~8.0 = fully diffuse blur. +vec3 envmap_color_lod(vec3 fragnormal, float lod) +{ +#if ENVMAP_ENABLED + vec3 refvec = reflect(f_pos.xyz, fragnormal); // view space — matches envmap_color exactly + refvec = vec3(inv_view * vec4(refvec, 0.0)); // world space — was missing + return textureLod(envmap, refvec, lod).rgb; +#else + return vec3(0.5); // was vec3(0.0), match the non-LOD fallback +#endif +} \ No newline at end of file diff --git a/shaders/light_common.glsl b/shaders/light_common.glsl index fb10aeda..bee46be9 100644 --- a/shaders/light_common.glsl +++ b/shaders/light_common.glsl @@ -10,6 +10,27 @@ uniform sampler2D headlightmap; float glossiness = 1.0; float metalic = 0.0; +// --------------------------------------------------------------------- +// Lighting balance tunables - tweak these to control overall scene +// exposure without touching tonemapping.glsl. +// +// AMBIENT_SCALE: brightness of SHADED faces (indirect/sky term). +// Lower -> deeper shadows, less burn under bright +// textures. Higher -> flatter / brighter shading. +// +// SUN_DIFFUSE_SCALE: brightness of UNSHADED (sun-lit) faces. Lower +// this to dim hot surfaces in direct sunlight +// without affecting shaded areas. Was 3.5; 2.5 +// calmly fits the ACES tonemap shoulder. +// +// SUN_NDOTL_SHARPNESS: N.L curve on the sun. 1.0 = pure Lambert, higher +// = sharper terminator (more contrast between +// lit and shaded faces of the same surface). +// --------------------------------------------------------------------- +const float AMBIENT_SCALE = 0.65; +const float SUN_DIFFUSE_SCALE = 2.5; +const float SUN_NDOTL_SHARPNESS = 1.25; + float length2(vec3 v) { return dot(v, v); @@ -60,7 +81,17 @@ vec2 calc_light(vec3 light_dir, vec3 fragnormal) vec3 halfway_dir = normalize(light_dir + view_dir); float diffuse_v = max(dot(fragnormal, light_dir), 0.0); - float specular_v = pow(max(dot(fragnormal, halfway_dir), 0.0), max(glossiness, 0.01)) * diffuse_v; + + // Energy-conserving Blinn-Phong normalization: + // (n+8)/(8*pi) ensures the specular lobe integrates to the same + // total energy regardless of glossiness — low glossiness stays dim + // and spreads wide (blurry), high glossiness is bright and tight (sharp). + // Capped at 4.0 so very high glossiness (n>~92) does not produce pinhole + // highlights that blow past the tonemap shoulder and read as burnt white. + float n = max(glossiness, 0.01); + float normalization = min((n + 8.0) / (8.0 * 3.14159265), 4.0); + float NdotH = max(dot(fragnormal, halfway_dir), 0.0); + float specular_v = normalization * pow(NdotH, n); return vec2(diffuse_v, specular_v); } @@ -108,7 +139,10 @@ vec2 calc_headlights(light_s light, vec3 fragnormal) return vec2(0.0); vec3 light_dir = normalize(light.pos - f_pos.xyz); - vec2 part = vec2(1.0) * clamp(dot(fragnormal, light_dir) + 0.25, 0.0, 1.0); + // Tighter wrap (was +0.25): faces angled away from the headlight cone + // fall off to dark much faster, so cab/exterior surfaces read with a + // clear directional shape instead of a flat half-lit wash. + vec2 part = vec2(1.0) * clamp(dot(fragnormal, light_dir) + 0.10, 0.0, 1.0); float distance = length(light.pos - f_pos.xyz); float atten = 1.0f / (1.0f + light.linear * distance + light.quadratic * (distance * distance)); atten *= mix(1.0, 0.0, clamp((coords.z - 0.998) * 500.0, 0.0, 1.0)); @@ -121,71 +155,75 @@ vec2 calc_headlights(light_s light, vec3 fragnormal) // do magic here vec3 apply_lights(vec3 fragcolor, vec3 fragnormal, vec3 texturecolor, float reflectivity, float specularity, float shadowtone) { - vec3 basecolor = param[0].rgb; + vec3 basecolor = param[0].rgb; + // Scale ambient before it gets tinted by basecolor / texture. + // Sun, headlights and emission are added afterwards so they are NOT + // attenuated by AMBIENT_SCALE - this only dims the indirect term. + fragcolor *= basecolor * AMBIENT_SCALE; - fragcolor *= basecolor; + vec3 emissioncolor = basecolor * emission; - vec3 emissioncolor = basecolor * emission; - vec3 envcolor = envmap_color(fragnormal); + vec3 view_dir = normalize(-f_pos.xyz); + float NdotV = max(dot(fragnormal, view_dir), 0.0); + vec3 F0 = mix(vec3(0.04), texturecolor, metalic); + vec3 fresnel = F0 + (1.0 - F0) * pow(1.0 - NdotV, 5.0); -// yuv path - vec3 texturecoloryuv = rgb2yuv(texturecolor); - vec3 texturecolorfullv = yuv2rgb(vec3(0.2176, texturecoloryuv.gb)); -// hsl path -// vec3 texturecolorhsl = rgb2hsl(texturecolor); -// vec3 texturecolorfullv = hsl2rgb(vec3(texturecolorhsl.rg, 0.5)); + const float MAX_REFLECTION_LOD = 8.0; + float env_roughness = 1.0 - clamp(glossiness / max(abs(param[1].w), 1.0), 0.0, 1.0); + vec3 envcolor = envmap_color_lod(fragnormal, env_roughness * MAX_REFLECTION_LOD); - vec3 envyuv = rgb2yuv(envcolor); - texturecolor = mix(texturecolor, texturecolorfullv, envyuv.r * reflectivity); + // Tint texture toward fully-saturated under strong env, weighted by fresnel + vec3 texturecoloryuv = rgb2yuv(texturecolor); + vec3 texturecolorfullv = yuv2rgb(vec3(0.2176, texturecoloryuv.gb)); + vec3 envyuv = rgb2yuv(envcolor); + texturecolor = mix(texturecolor, texturecolorfullv, envyuv.r * reflectivity * fresnel.r); - if(lights_count == 0U) - return (fragcolor + emissioncolor + envcolor * reflectivity) * texturecolor; + if (lights_count == 0U) + return (fragcolor + emissioncolor) * texturecolor + + envcolor * fresnel * reflectivity; -// fragcolor *= lights[0].intensity; - vec2 sunlight = calc_dir_light(lights[0], fragnormal); + vec2 sunlight = calc_dir_light(lights[0], fragnormal); + // Sharpen sun N.L falloff so the lit-to-shaded terminator on cab + // panels, vehicle bodies and terrain reads as a clear edge rather + // than a soft Lambertian ramp. Tunable via SUN_NDOTL_SHARPNESS. + float sun_NdotL = pow(sunlight.x, SUN_NDOTL_SHARPNESS); + float diffuseamount = sun_NdotL * param[1].x * lights[0].intensity; - float diffuseamount = (sunlight.x * param[1].x) * lights[0].intensity; -// fragcolor += mix(lights[0].color * diffuseamount, envcolor, reflectivity); - float shadow1 = 0.0; - if (shadowtone < 1.0) - { - shadow1 = (1 - shadowtone) * clamp(calc_shadow(), 0.0, 1.00); - } - fragcolor += lights[0].color * 5.0 * (1.0 - shadow1) * diffuseamount; - fragcolor = mix(fragcolor * 0.35, envcolor, reflectivity); + float shadow1 = 0.0; + if (shadowtone < 1.0) + shadow1 = (1.0 - shadowtone) * clamp(calc_shadow(), 0.0, 1.0); - for (uint i = 1U; i < lights_count; i++) - { - light_s light = lights[i]; - vec2 part = vec2(0.0); + // Sun HDR scale -> SUN_DIFFUSE_SCALE (default 2.5). Controls how + // bright sun-lit (unshaded) faces get. Lower this if surfaces in + // direct sun read as too hot/burnt; raise it for more punch. + fragcolor += lights[0].color * SUN_DIFFUSE_SCALE * (1.0 - shadow1) * diffuseamount; -// if (light.type == LIGHT_SPOT) -// part = calc_spot_light(light, fragnormal); -// else if (light.type == LIGHT_POINT) -// part = calc_point_light(light, fragnormal); -// else if (light.type == LIGHT_DIR) -// part = calc_dir_light(light, fragnormal); -// else if (light.type == LIGHT_HEADLIGHTS) - part = calc_headlights(light, fragnormal); + for (uint i = 1U; i < lights_count; i++) + { + light_s light = lights[i]; + vec2 part = calc_headlights(light, fragnormal); + fragcolor += light.color * (part.x * param[1].x + part.y * param[1].y) * light.intensity; + } - fragcolor += light.color * (part.x * param[1].x + part.y * param[1].y) * light.intensity; - } + float specularamount = sunlight.y * param[1].y * specularity * lights[0].intensity + * clamp(1.0 - shadowtone, 0.0, 1.0); + if (shadowtone < 1.0) + specularamount *= clamp(1.0 - shadow1, 0.0, 1.0); - float specularamount = (sunlight.y * param[1].y * specularity) * lights[0].intensity * clamp(1.0 - shadowtone, 0.0, 1.0); - if (shadowtone < 1.0) - { - //float shadow = calc_shadow(); - specularamount *= clamp(1.0 - shadow1, 0.0, 1.00); - //fragcolor = mix(fragcolor, fragcolor * shadowtone, clamp((diffuseamount) * shadow1 , 0.0, 1.0)); - } - fragcolor += emissioncolor; - vec3 specularcolor = specularamount * lights[0].color; + fragcolor += emissioncolor; + vec3 specularcolor = specularamount * lights[0].color; - if (param[1].w < 0.0) - { - float metalic = 1.0; - } - fragcolor = mix(((fragcolor + specularcolor) * texturecolor),(fragcolor * texturecolor + specularcolor),metalic) ; + // Env reflection tracked separately — must NOT go through the albedo multiply below + vec3 env_reflection = envcolor * fresnel * reflectivity * (1.0 - shadow1 * 0.5); - return fragcolor; -} + // Diffuse + specular: albedo tints diffuse, metals also tint specular + vec3 result = mix( + (fragcolor + specularcolor) * texturecolor, + fragcolor * texturecolor + specularcolor, + metalic); + + // Env added after albedo multiply: raw for dielectrics, albedo-tinted for metals + result += mix(env_reflection, env_reflection * texturecolor, metalic); + + return result; +} \ No newline at end of file diff --git a/shaders/mat_default_specgloss.frag b/shaders/mat_default_specgloss.frag index cb7a2e5a..254a0cc7 100644 --- a/shaders/mat_default_specgloss.frag +++ b/shaders/mat_default_specgloss.frag @@ -43,7 +43,7 @@ void main() float reflectivity = param[1].z; float specularity = texture(specgloss, f_coord).r; glossiness = texture(specgloss, f_coord).g * abs(param[1].w); - float metalic = texture(specgloss, f_coord).b; + metalic = texture(specgloss, f_coord).b; fragcolor = apply_lights(fragcolor, fragnormal, tex_color.rgb, reflectivity, specularity, shadow_tone); vec4 color = vec4(apply_fog(fragcolor), tex_color.a * alpha_mult); diff --git a/shaders/mat_detail_normalmap_specgloss.frag b/shaders/mat_detail_normalmap_specgloss.frag index fbd90257..a3799ce1 100644 --- a/shaders/mat_detail_normalmap_specgloss.frag +++ b/shaders/mat_detail_normalmap_specgloss.frag @@ -68,7 +68,7 @@ void main() float reflectivity = reflblend > 1.0 ? param[1].z * 1.0 : param[1].z * reflblend; float specularity = specgloss_map.r; glossiness = specgloss_map.g * abs(param[1].w); - float metalic = specgloss_map.b; + metalic = specgloss_map.b; fragcolor = apply_lights(fragcolor, fragnormal, tex_color.rgb, reflectivity, specularity, shadow_tone); diff --git a/shaders/mat_detail_parallax_specgloss.frag b/shaders/mat_detail_parallax_specgloss.frag index 416a6854..fcc33ac3 100644 --- a/shaders/mat_detail_parallax_specgloss.frag +++ b/shaders/mat_detail_parallax_specgloss.frag @@ -73,8 +73,8 @@ void main() vec3 fragnormal = normalize(f_tbn * normalize(vec3(normal.xy + normaldetail.xy, normal.z))); float reflectivity = param[1].z * normal_map.a; float specularity = specgloss_map.r; - float glossiness = specgloss_map.g * abs(param[1].w); - float metalic = specgloss_map.b; + glossiness = specgloss_map.g * abs(param[1].w); + metalic = specgloss_map.b; fragcolor = apply_lights(fragcolor, fragnormal, tex_color.rgb, reflectivity, specularity, shadow_tone); diff --git a/shaders/mat_normalmap_specgloss.frag b/shaders/mat_normalmap_specgloss.frag index 86085df2..a4b18462 100644 --- a/shaders/mat_normalmap_specgloss.frag +++ b/shaders/mat_normalmap_specgloss.frag @@ -53,7 +53,7 @@ void main() float reflectivity = param[1].z * texture(normalmap, f_coord).a; float specularity = texture(specgloss, f_coord).r; glossiness = texture(specgloss, f_coord).g * abs(param[1].w); - float metalic = texture(specgloss, f_coord).b; + metalic = texture(specgloss, f_coord).b; fragcolor = apply_lights(fragcolor, fragnormal, tex_color.rgb, reflectivity, specularity, shadow_tone); diff --git a/shaders/mat_parallax_specgloss.frag b/shaders/mat_parallax_specgloss.frag index 4ce17da7..8b590fae 100644 --- a/shaders/mat_parallax_specgloss.frag +++ b/shaders/mat_parallax_specgloss.frag @@ -61,7 +61,7 @@ void main() float reflectivity = param[1].z * texture(normalmap, f_coord_p).a; float specularity = texture(specgloss, f_coord_p).r; glossiness = texture(specgloss, f_coord_p).g * abs(param[1].w); - float metalic = texture(specgloss, f_coord_p).b; + metalic = texture(specgloss, f_coord_p).b; fragcolor = apply_lights(fragcolor, fragnormal, tex_color.rgb, reflectivity, specularity, shadow_tone); diff --git a/shaders/mat_reflmap_specgloss.frag b/shaders/mat_reflmap_specgloss.frag index b5f9bc38..4c89083c 100644 --- a/shaders/mat_reflmap_specgloss.frag +++ b/shaders/mat_reflmap_specgloss.frag @@ -47,7 +47,7 @@ void main() float reflectivity = param[1].z * texture(reflmap, f_coord).a; float specularity = texture(specgloss, f_coord).r; glossiness = texture(specgloss, f_coord).g * abs(param[1].w); - float metalic = texture(specgloss, f_coord).b; + metalic = texture(specgloss, f_coord).b; fragcolor = apply_lights(fragcolor, fragnormal, tex_color.rgb, reflectivity, specularity, shadow_tone); diff --git a/shaders/mat_shadowlessnormalmap_specgloss.frag b/shaders/mat_shadowlessnormalmap_specgloss.frag index 58175c7c..adc473ff 100644 --- a/shaders/mat_shadowlessnormalmap_specgloss.frag +++ b/shaders/mat_shadowlessnormalmap_specgloss.frag @@ -53,7 +53,7 @@ void main() float reflectivity = param[1].z * texture(normalmap, f_coord).a; float specularity = texture(specgloss, f_coord).r; glossiness = texture(specgloss, f_coord).g * abs(param[1].w); - float metalic = texture(specgloss, f_coord).b; + metalic = texture(specgloss, f_coord).b; fragcolor = apply_lights(fragcolor, fragnormal, tex_color.rgb, reflectivity, specularity, 1.0); vec4 color = vec4(apply_fog(fragcolor), tex_color.a * alpha_mult); diff --git a/shaders/mat_sunlessnormalmap.frag b/shaders/mat_sunlessnormalmap.frag index 4f4e3bd8..c0f20f5f 100644 --- a/shaders/mat_sunlessnormalmap.frag +++ b/shaders/mat_sunlessnormalmap.frag @@ -34,7 +34,10 @@ vec3 apply_lights_sunless(vec3 fragcolor, vec3 fragnormal, vec3 texturecolor, fl { vec3 basecolor = param[0].rgb; - fragcolor *= basecolor; + // Cab interior: dim ambient less than the exterior path because + // ambient is the dominant indoor illumination. 0.80 trims the + // brightest faces without making the cab feel under-lit. + fragcolor *= basecolor * 0.80; vec3 emissioncolor = basecolor * emission; vec3 envcolor = envmap_color(fragnormal); @@ -54,7 +57,10 @@ vec3 apply_lights_sunless(vec3 fragcolor, vec3 fragnormal, vec3 texturecolor, fl vec2 sunlight = calc_dir_light(lights[0], fragnormal); - float diffuseamount = (sunlight.x * param[1].x) * lights[0].intensity; + // Sharpen N.L for stronger contrast between lit and shaded cab + // surfaces (uses SUN_NDOTL_SHARPNESS from light_common.glsl). + float sun_NdotL = pow(sunlight.x, SUN_NDOTL_SHARPNESS); + float diffuseamount = (sun_NdotL * param[1].x) * lights[0].intensity; fragcolor += envcolor * reflectivity; float specularamount = (sunlight.y * param[1].y * specularity) * lights[0].intensity; glossiness = abs(param[1].w); diff --git a/shaders/mat_sunlessnormalmap_specgloss.frag b/shaders/mat_sunlessnormalmap_specgloss.frag index cf5a7433..3da46808 100644 --- a/shaders/mat_sunlessnormalmap_specgloss.frag +++ b/shaders/mat_sunlessnormalmap_specgloss.frag @@ -36,59 +36,67 @@ uniform sampler2D specgloss; vec3 apply_lights_sunless(vec3 fragcolor, vec3 fragnormal, vec3 texturecolor, float reflectivity, float specularity, float shadowtone) { - vec3 basecolor = param[0].rgb; + vec3 basecolor = param[0].rgb; + // Cab interior: dim ambient less than the exterior path - ambient + // is the dominant indoor illumination, but it was still too bright. + fragcolor *= basecolor * 0.80; - fragcolor *= basecolor; + vec3 emissioncolor = basecolor * emission; - vec3 emissioncolor = basecolor * emission; - vec3 envcolor = envmap_color(fragnormal); + vec3 view_dir = normalize(-f_pos.xyz); + float NdotV = max(dot(fragnormal, view_dir), 0.0); + vec3 F0 = mix(vec3(0.04), texturecolor, metalic); + vec3 fresnel = F0 + (1.0 - F0) * pow(1.0 - NdotV, 5.0); -// yuv path - vec3 texturecoloryuv = rgb2yuv(texturecolor); - vec3 texturecolorfullv = yuv2rgb(vec3(0.2176, texturecoloryuv.gb)); -// hsl path -// vec3 texturecolorhsl = rgb2hsl(texturecolor); -// vec3 texturecolorfullv = hsl2rgb(vec3(texturecolorhsl.rg, 0.5)); + const float MAX_REFLECTION_LOD = 8.0; + float env_roughness = 1.0 - clamp(glossiness / max(abs(param[1].w), 1.0), 0.0, 1.0); + vec3 envcolor = envmap_color_lod(fragnormal, env_roughness * MAX_REFLECTION_LOD); - vec3 envyuv = rgb2yuv(envcolor); - texturecolor = mix(texturecolor, texturecolorfullv, envyuv.r * reflectivity); + vec3 texturecoloryuv = rgb2yuv(texturecolor); + vec3 texturecolorfullv = yuv2rgb(vec3(0.2176, texturecoloryuv.gb)); + vec3 envyuv = rgb2yuv(envcolor); + texturecolor = mix(texturecolor, texturecolorfullv, envyuv.r * reflectivity * fresnel.r); - if(lights_count == 0U) - return (fragcolor + emissioncolor + envcolor * reflectivity) * texturecolor; + if (lights_count == 0U) + return (fragcolor + emissioncolor) * texturecolor + + envcolor * fresnel * reflectivity; - vec2 sunlight = calc_dir_light(lights[0], fragnormal); + vec2 sunlight = calc_dir_light(lights[0], fragnormal); + // Sharpen N.L for stronger contrast between lit and shaded cab + // surfaces (uses SUN_NDOTL_SHARPNESS from light_common.glsl). + float sun_NdotL = pow(sunlight.x, SUN_NDOTL_SHARPNESS); + float diffuseamount = sun_NdotL * param[1].x * lights[0].intensity; - float diffuseamount = (sunlight.x * param[1].x) * lights[0].intensity; - fragcolor += envcolor * reflectivity; - float specularamount = (sunlight.y * param[1].y * specularity) * lights[0].intensity; + float specularamount = sunlight.y * param[1].y * specularity * lights[0].intensity; - for (uint i = 1U; i < lights_count; i++) - { - light_s light = lights[i]; - vec2 part = vec2(0.0); + for (uint i = 1U; i < lights_count; i++) + { + light_s light = lights[i]; + vec2 part = calc_headlights(light, fragnormal); + fragcolor += light.color * (part.x * param[1].x + part.y * param[1].y) * light.intensity; + } -// if (light.type == LIGHT_SPOT) -// part = calc_spot_light(light, fragnormal); -// else if (light.type == LIGHT_POINT) -// part = calc_point_light(light, fragnormal); -// else if (light.type == LIGHT_DIR) -// part = calc_dir_light(light, fragnormal); -// else if (light.type == LIGHT_HEADLIGHTS) - part = calc_headlights(light, fragnormal); + if (shadowtone < 1.0) + { + float shadow = calc_shadow(); + specularamount *= clamp(1.0 - shadow, 0.0, 1.0); + fragcolor = mix(fragcolor, fragcolor * shadowtone, clamp(diffuseamount * shadow, 0.0, 1.0)); + } - fragcolor += light.color * (part.x * param[1].x + part.y * param[1].y) * light.intensity; - } + fragcolor += emissioncolor; + vec3 specularcolor = specularamount * lights[0].color; - if (shadowtone < 1.0) - { - float shadow = calc_shadow(); - specularamount *= clamp(1.0 - shadow, 0.0, 1.0); - fragcolor = mix(fragcolor, fragcolor * shadowtone, clamp(diffuseamount * shadow + specularamount, 0.0, 1.0)); - } - fragcolor += emissioncolor; - fragcolor *= texturecolor; - - return fragcolor; + // Env reflection separate from albedo multiply — same fix as apply_lights + vec3 env_reflection = envcolor * fresnel * reflectivity; + + vec3 result = mix( + (fragcolor + specularcolor) * texturecolor, + fragcolor * texturecolor + specularcolor, + metalic); + + result += mix(env_reflection, env_reflection * texturecolor, metalic); + + return result; } void main() @@ -110,7 +118,7 @@ void main() float reflectivity = param[1].z * texture(normalmap, f_coord).a; float specularity = texture(specgloss, f_coord).r; glossiness = texture(specgloss, f_coord).g * abs(param[1].w); - float metalic = texture(specgloss, f_coord).b; + metalic = texture(specgloss, f_coord).b; fragcolor = apply_lights_sunless(fragcolor, fragnormal, tex_color.rgb, reflectivity, specularity, shadow_tone); vec4 color = vec4(apply_fog(fragcolor), tex_color.a * alpha_mult); diff --git a/shaders/postfx_ssao.frag b/shaders/postfx_ssao.frag new file mode 100644 index 00000000..05ff9692 --- /dev/null +++ b/shaders/postfx_ssao.frag @@ -0,0 +1,72 @@ +in vec2 f_coords; +layout(location = 0) out vec4 out_color; + +#texture (depth_tex, 0, R) +uniform sampler2D depth_tex; + +#texture (noise_tex, 1, RGB) +uniform sampler2D noise_tex; + +#include + +vec3 view_pos_from_depth(vec2 uv, float depth) { + vec4 clip = vec4(uv * 2.0 - 1.0, depth * 2.0 - 1.0, 1.0); + vec4 view = inverse(projection) * clip; + return view.xyz / view.w; +} + +// Deterministic hemisphere sample (z > 0), mimics the C++ kernel generator +vec3 kernel_sample(int i) { + float fi = float(i); + vec3 dir = normalize(vec3( + fract(sin(fi * 12.9898) * 43758.5453) * 2.0 - 1.0, + fract(sin(fi * 78.2330) * 43758.5453) * 2.0 - 1.0, + fract(sin(fi * 37.7190) * 43758.5453) // z >= 0 -> hemisphere + )); + float len = fract(sin(fi * 94.6720) * 43758.5453); + float scale = fi / 32.0; + scale = mix(0.1, 1.0, scale * scale); // bias samples inward + return dir * len * scale; +} + +void main() { + float d = texture(depth_tex, f_coords).r; + if (d >= 1.0) { out_color = vec4(1.0); return; } // skybox -> no occlusion + + vec3 pos = view_pos_from_depth(f_coords, d); + + vec3 ddx = dFdx(pos); + vec3 ddy = dFdy(pos); + vec3 n = normalize(cross(ddx, ddy)); + + // derive screen size from the depth texture instead of a UBO field + vec2 screen_size = vec2(textureSize(depth_tex, 0)); + vec2 noise_uv = f_coords * (screen_size / 4.0); + vec3 rvec = texture(noise_tex, noise_uv).xyz; + + vec3 t = normalize(rvec - n * dot(rvec, n)); + vec3 b = cross(n, t); + mat3 TBN = mat3(t, b, n); + + const int KERNEL = 32; + const float RADIUS = 0.5; + const float BIAS = 0.025; + + float occ = 0.0; + for (int i = 0; i < KERNEL; ++i) { + vec3 sp = TBN * kernel_sample(i); + sp = pos + sp * RADIUS; + + vec4 clip = projection * vec4(sp, 1.0); + vec3 ndc = clip.xyz / clip.w; + vec2 suv = ndc.xy * 0.5 + 0.5; + + float sd = texture(depth_tex, suv).r; + float szv = view_pos_from_depth(suv, sd).z; + + float range = smoothstep(0.0, 1.0, RADIUS / abs(pos.z - szv)); + occ += (szv >= sp.z + BIAS ? 1.0 : 0.0) * range; + } + + out_color = vec4(1.0 - occ / float(KERNEL)); +} \ No newline at end of file diff --git a/shaders/postfx_ssao_apply.frag b/shaders/postfx_ssao_apply.frag new file mode 100644 index 00000000..177c5b29 --- /dev/null +++ b/shaders/postfx_ssao_apply.frag @@ -0,0 +1,22 @@ +in vec2 f_coords; +layout(location = 0) out vec4 out_color; + +#texture (color_tex, 0, RGB) +uniform sampler2D color_tex; + +#texture (ssao_tex, 1, R) +uniform sampler2D ssao_tex; + +const float SSAO_STRENGTH = 0.8; +const float SSAO_MIN = 0.3; // floor on darkening so shadows don't go pitch black + +void main() { + vec3 c = texture(color_tex, f_coords).rgb; + float occ = texture(ssao_tex, f_coords).r; + + // remap occlusion with strength and a minimum floor + occ = pow(clamp(occ, 0.0, 1.0), SSAO_STRENGTH); + occ = mix(SSAO_MIN, 1.0, occ); + + out_color = vec4(c * occ, 1.0); +} \ No newline at end of file diff --git a/shaders/postfx_ssao_blur.frag b/shaders/postfx_ssao_blur.frag new file mode 100644 index 00000000..e3cbfb0b --- /dev/null +++ b/shaders/postfx_ssao_blur.frag @@ -0,0 +1,14 @@ +in vec2 f_coords; +layout(location = 0) out vec4 out_color; + +#texture (ssao_tex, 0, R) +uniform sampler2D ssao_tex; + +void main() { + vec2 texel = 1.0 / vec2(textureSize(ssao_tex, 0)); + float sum = 0.0; + for (int x = -2; x < 2; ++x) + for (int y = -2; y < 2; ++y) + sum += texture(ssao_tex, f_coords + vec2(x, y) * texel).r; + out_color = vec4(sum / 16.0); +} \ No newline at end of file diff --git a/shaders/tonemapping.glsl b/shaders/tonemapping.glsl index de01a258..27fff21b 100644 --- a/shaders/tonemapping.glsl +++ b/shaders/tonemapping.glsl @@ -42,6 +42,10 @@ vec3 filmic(vec3 x) vec4 tonemap(vec4 x) { -// return FBOUT(vec4(ACESFilm(x.rgb), x.a)); - return FBOUT(vec4(reinhard(x.rgb), x.a)); + // Use ACES Filmic by default. Reinhard above kept for reference, but + // with pureWhite=1.0 it collapses to identity (L*(1+L)/(1+L) = L) and + // just clips HDR>1.0 at the framebuffer -> washed-out / burnt look. + // ACES gives a smooth highlight shoulder + slight toe contrast. + return FBOUT(vec4(ACESFilm(x.rgb), x.a)); + //return FBOUT(vec4(reinhard(x.rgb), x.a)); } diff --git a/utilities/Globals.h b/utilities/Globals.h index efbdc29d..590b9354 100644 --- a/utilities/Globals.h +++ b/utilities/Globals.h @@ -294,6 +294,7 @@ struct global_settings { std::string exec_on_exit; std::string prepend_scn; + bool gfx_postfx_ssao_enabled {false}; struct extraviewport_config { std::string monitor; diff --git a/vehicle/Train.cpp b/vehicle/Train.cpp index de2e8e5e..d385a540 100644 --- a/vehicle/Train.cpp +++ b/vehicle/Train.cpp @@ -450,6 +450,7 @@ TTrain::commandhandler_map const TTrain::m_commandhandlers = { { user_command::radiostopenable, &TTrain::OnCommand_radiostopenable }, { user_command::radiostopdisable, &TTrain::OnCommand_radiostopdisable }, { user_command::radiostoptest, &TTrain::OnCommand_radiostoptest }, + { user_command::radiocall1send, &TTrain::OnCommand_radiocall1send }, { user_command::radiocall3send, &TTrain::OnCommand_radiocall3send }, { user_command::radiovolumeincrease, &TTrain::OnCommand_radiovolumeincrease }, { user_command::radiovolumedecrease, &TTrain::OnCommand_radiovolumedecrease }, @@ -6917,6 +6918,23 @@ void TTrain::OnCommand_radiostoptest( TTrain *Train, command_data const &Command } } +void TTrain::OnCommand_radiocall1send( TTrain *Train, command_data const &Command ) +{ + if( Command.action == GLFW_PRESS ) { + if( ( Train->RadioChannel() != 10 ) + && ( true == Train->mvOccupied->Radio ) + && ( Train->mvOccupied->Power24vIsAvailable || Train->mvOccupied->Power110vIsAvailable) ) { + simulation::Events.queue_receivers( radio_message::call1, Train->Dynamic()->GetPosition() ); + } + // visual feedback + Train->ggRadioCall1.UpdateValue( 1.0 ); + } + else if( Command.action == GLFW_RELEASE ) { + // visual feedback + Train->ggRadioCall1.UpdateValue( 0.0 ); + } +} + void TTrain::OnCommand_radiocall3send( TTrain *Train, command_data const &Command ) { if( Command.action == GLFW_PRESS ) { @@ -8343,6 +8361,7 @@ bool TTrain::Update( double const Deltatime ) ggRadioChannelNext.Update(); ggRadioStop.Update(); ggRadioTest.Update(); + ggRadioCall1.Update(); ggRadioCall3.Update(); ggRadioVolumeSelector.Update(); ggRadioVolumePrevious.Update(); @@ -9861,6 +9880,7 @@ void TTrain::clear_cab_controls() ggRadioChannelNext.Clear(); ggRadioStop.Clear(); ggRadioTest.Clear(); + ggRadioCall1.Clear(); ggRadioCall3.Clear(); ggRadioVolumeSelector.Clear(); ggRadioVolumePrevious.Clear(); @@ -10668,6 +10688,7 @@ bool TTrain::initialize_gauge(cParser &Parser, std::string const &Label, int con { "radiochannelnext_sw:", ggRadioChannelNext }, { "radiostop_sw:", ggRadioStop }, { "radiotest_sw:", ggRadioTest }, + { "radiocall1_sw:", ggRadioCall1 }, { "radiocall3_sw:", ggRadioCall3 }, { "radiovolume_sw:", ggRadioVolumeSelector }, { "radiovolumeprev_sw:", ggRadioVolumePrevious }, diff --git a/vehicle/Train.h b/vehicle/Train.h index 96a41463..916d761b 100644 --- a/vehicle/Train.h +++ b/vehicle/Train.h @@ -475,6 +475,7 @@ class TTrain { static void OnCommand_radiostopenable( TTrain *Train, command_data const &Command ); static void OnCommand_radiostopdisable( TTrain *Train, command_data const &Command ); static void OnCommand_radiostoptest( TTrain *Train, command_data const &Command ); + static void OnCommand_radiocall1send( TTrain *Train, command_data const &Command ); static void OnCommand_radiocall3send( TTrain *Train, command_data const &Command ); static void OnCommand_radiovolumeincrease(TTrain *Train, command_data const &Command); static void OnCommand_radiovolumedecrease(TTrain *Train, command_data const &Command); @@ -580,6 +581,7 @@ public: // reszta może by?publiczna TGauge ggRadioChannelNext; TGauge ggRadioTest; TGauge ggRadioStop; + TGauge ggRadioCall1; TGauge ggRadioCall3; TGauge ggRadioVolumeSelector; TGauge ggRadioVolumePrevious; diff --git a/vr/openvr_imp.cpp b/vr/openvr_imp.cpp index 5e09547c..088fd5b5 100644 --- a/vr/openvr_imp.cpp +++ b/vr/openvr_imp.cpp @@ -687,9 +687,12 @@ std::unordered_map vr_openvr::m_buttonb { "radiotest_sw:", { user_command::radiostoptest, user_command::none } }, - { "radiocall3_sw:", { - user_command::radiocall3send, - user_command::none } }, + { "radiocall1_sw:", { + user_command::radiocall1send, + user_command::none } }, + { "radiocall3_sw:", { + user_command::radiocall3send, + user_command::none } }, { "radiovolume_sw:",{ user_command::radiovolumeincrease, user_command::radiovolumedecrease } },