diff --git a/docs/opengl46-renderer.md b/docs/opengl46-renderer.md index 5b5af057..4ca27a2b 100644 --- a/docs/opengl46-renderer.md +++ b/docs/opengl46-renderer.md @@ -187,6 +187,35 @@ shadows of the sun, see `headlight shadows:` in Shift+F12); with the vehicle on | `gfx.lights.shadows.headlights` | `2` | 0..4: how many sets of headlights (vehicle ends) cast shadows; 0: none | | `gfx.lights.shadows.headlights.range` | `250` | 10..1000: distance ahead of the vehicle within which things cast the shadows of its headlights, metres | +### Contact shadows + +Short shadows where things touch or nearly touch: the rails on the sleepers, bolts and edges of panels, a wheel on the rail, +a post in the ground, the knobs on the desk of the cab. The shadow maps are too coarse for them, and a surface right next to +another one is kept from shadowing itself there. A contact shadow is found in the image: from each pixel a short ray goes +towards the light, `gfx.contactshadows.length` long, and where the depth of the scene drawn so far holds a surface in front +of it, the light is held back. So only what is in the picture casts them (nothing out of view or behind something else), +they fade over the end of the ray, and they end at `gfx.contactshadows.range` from the camera. They need the depth of the +whole scene before its colour, so turning them on turns on the depth pass of `gfx.depthprepass.scene` (whatever that is set +to; see its cost below); they are there only in the main view, with the post processing on and not in the orthographic view +of the editor. The things left out of the depth pass (parallax materials, water, glass, faded parts) cast none. + +| Level | Rays | +|---|---| +| `low` (1) | towards the sun (or the moon), 8 steps | +| `medium` (2) | towards the sun, 12 steps, and towards 2 lamps or headlights which light the pixel, 8 steps | +| `high` (3) | towards the sun, 16 steps, and towards 4 lamps or headlights, 12 steps | + +The rays towards a lamp go at most half the way to it. The lamps of a pixel are the first which light it noticeably, in the +order of the light buffer: the cab lamp, the headlights, then the lamps of models and vehicles, the most important first. + +| Entry | Default | Meaning | +|---|---|---| +| `gfx.contactshadows` | `no` | `no`/`low`/`medium`/`high` (or 0..3), see above | +| `gfx.contactshadows.length` | `0.5` | 0.05..5: length of the rays, metres | +| `gfx.contactshadows.range` | `50` | 1..1000: distance from the camera within which there are contact shadows, metres | + +All three can be changed while the simulator runs, in the **Light clusters** debug window. + ### Weather The clouds, the fog and the stars are drawn from the weather of the simulation (a `weather` section of the scenery, or its @@ -329,6 +358,10 @@ editor goes through the first `dynamiclights` headlights, the reflections throug models both counted and how many levels of detail differ. - With `gfx.depthprepass.scene yes` Shift+F12 shows the `scene depth:` row (draws of the depth pass and its processor time) and the timing `scene depth:` (its GPU time). +- With `gfx.contactshadows` on Shift+F12 shows the `contact shadows:` row (the level, the length and reach of the rays, or + `off in this view`), the `scene depth:` row and timing of the depth pass they turn on, and the timing `contact shadows depth:` + (copying the depth for them). The rays themselves are part of the shading of the colour pass: their cost shows as the + difference in the GPU frame time with them on and off. - Shift+F12 shows the `instance buffer:` row (positions of copies written in the frame and the size of the buffer) and the `model blocks:` row: how many blocks of model data the frame wrote, with `gfx.bufferstreaming yes` also how much of the buffer of the frame they took, how many times a frame had to wait for the graphics card to finish with a buffer before diff --git a/gl/glsl_common.cpp b/gl/glsl_common.cpp index 7918a401..e7a27328 100644 --- a/gl/glsl_common.cpp +++ b/gl/glsl_common.cpp @@ -70,6 +70,7 @@ void gl::glsl_common_setup() "#define CLOUD_DETAIL_TEX " + std::to_string(CLOUD_DETAIL_TEX) + "\n" + "#define CLOUD_COVERAGE_TEX " + std::to_string(CLOUD_COVERAGE_TEX) + "\n" + "#define CLOUD_HISTORY_TEX " + std::to_string(CLOUD_HISTORY_TEX) + "\n" + + "#define SCENE_DEPTH_TEX " + std::to_string(SCENE_DEPTH_TEX) + "\n" + "#define WEATHER_UBO_BINDING " + std::to_string(WEATHER_UBO_BINDING) + "\n" + "#define WEATHER_SCRATCH_SSBO_BINDING " + std::to_string(WEATHER_SCRATCH_SSBO_BINDING) + "\n" + "#define MAX_CLOUD_LAYERS " + std::to_string(MAX_CLOUD_LAYERS) + "\n" + @@ -214,6 +215,9 @@ void gl::glsl_common_setup() // shadow maps of the headlights, see gl::scene_ubs mat4 headlight_shadow[MAX_HEADLIGHT_SHADOWS]; vec4 headlight_shadow_params[MAX_HEADLIGHT_SHADOWS]; + // contact shadows, see gl::scene_ubs + vec4 contact_shadow_params; + vec4 contact_shadow_lamps; }; #ifdef STAGE_VERTEX diff --git a/gl/ubo.h b/gl/ubo.h index b3625bcc..b2acfc88 100644 --- a/gl/ubo.h +++ b/gl/ubo.h @@ -75,6 +75,9 @@ namespace gl const size_t CLOUD_DETAIL_TEX = MAX_TEXTURES + HELPER_TEXTURES + 8; const size_t CLOUD_COVERAGE_TEX = MAX_TEXTURES + HELPER_TEXTURES + 9; const size_t CLOUD_HISTORY_TEX = MAX_TEXTURES + HELPER_TEXTURES + 10; + // the depth of the opaque scene of the main view drawn before its colour, resolved, for the contact shadows (light_common.glsl). + // bound directly, like the ones above + const size_t SCENE_DEPTH_TEX = MAX_TEXTURES + HELPER_TEXTURES + 11; struct scene_ubs { @@ -92,9 +95,15 @@ namespace gl // projector. and x: size of a texel of the map one metre from the projector, y: radius of the filter in texels glm::mat4 headlight_shadow[MAX_HEADLIGHT_SHADOWS]; glm::vec4 headlight_shadow_params[MAX_HEADLIGHT_SHADOWS]; + // contact shadows, read from SCENE_DEPTH_TEX; set only for the colour pass of the main view, zero elsewhere. + // x: length of the rays towards the lights, metres (0: off), y: distance from the camera at which they end, z: steps + // of the ray towards the sun, w: steps of the rays towards lamps and headlights + glm::vec4 contact_shadow_params; + // x: how many lamps and headlights a pixel traces the rays to (0: only the sun), yzw unused + glm::vec4 contact_shadow_lamps; }; - static_assert(sizeof(scene_ubs) == 400 + 80 * MAX_HEADLIGHT_SHADOWS, "bad size of ubs"); + static_assert(sizeof(scene_ubs) == 432 + 80 * MAX_HEADLIGHT_SHADOWS, "bad size of ubs"); const size_t MAX_PARAMS = 3; diff --git a/rendering/opengl46/opengl46renderer.cpp b/rendering/opengl46/opengl46renderer.cpp index b0953a55..32fd6437 100644 --- a/rendering/opengl46/opengl46renderer.cpp +++ b/rendering/opengl46/opengl46renderer.cpp @@ -740,10 +740,14 @@ void opengl46_renderer::SwapBuffers() m_debugtimestext += m_exposure.info_times(); m_debugtimestext += m_ao.info_times(); m_debugtimestext += m_instancecull.info_times(); - if( Global.gfx_depthprepass_scene ) { + if( Global.gfx_depthprepass_scene || m_contactshadowslast ) { if( auto const scenedepthtime = m_scenedepthtimer.result() ) m_debugtimestext += " scene depth: " + to_string( static_cast( *scenedepthtime ) / 1000000.0, 3 ) + " ms\n"; } + if( m_contactshadowslast ) { + if( auto const contacttime = m_contactshadowstimer.result() ) + m_debugtimestext += " contact shadows depth: " + to_string( static_cast( *contacttime ) / 1000000.0, 3 ) + " ms\n"; + } if( Global.gfx_gpucull == 1 ) { m_debugtimestext += m_hiz.info_times(); if( auto const prepasstime = m_prepasstimer.result() ) @@ -770,9 +774,15 @@ void opengl46_renderer::SwapBuffers() + " instanced:" + to_string( m_colorpass.draw_stats.instances, 7 ) + " +" + to_string( shadowstats.instances, 7 ) + " =" + to_string( m_colorpass.draw_stats.instances + shadowstats.instances, 7 ) + " (" + std::to_string( m_colorpass.draw_stats.instanced_drawcalls + shadowstats.instanced_drawcalls ) + " batches)\n" - + ( Global.gfx_depthprepass_scene ? + + ( ( Global.gfx_depthprepass_scene || m_contactshadowslast ) ? " scene depth: " + std::to_string( m_scenedepthstats.drawcalls ) + " draws, cpu " + to_string( m_scenedepthcputime, 2 ) + " ms\n" : std::string{} ) + + ( Global.gfx_contactshadows > 0 ? + " contact shadows: " + std::string{ Global.gfx_contactshadows == 3 ? "high" : Global.gfx_contactshadows == 2 ? "medium" : "low" } + + ( m_contactshadowslast ? + ", rays " + to_string( Global.gfx_contactshadows_length, 2 ) + " m within " + to_string( Global.gfx_contactshadows_range, 0 ) + " m\n" : + ", off in this view\n" ) : + std::string{} ) + " instance buffer: " + std::to_string( m_instancestream->used() / static_cast( sizeof( glm::mat4 ) ) ) + " matrices, " + std::to_string( m_instancestream->size() / 1024 ) + " KiB\n" + " model blocks: " + std::to_string( model_ubo->updates() ) @@ -853,6 +863,11 @@ void opengl46_renderer::draw_debug_ui() m_locallights.draw_debug_ui(); ImGui::Separator(); m_localshadows.draw_debug_ui(); + ImGui::Separator(); + char const *contactlevels[]{"no", "low (the sun)", "medium (the sun and 2 lamps)", "high (the sun and 4 lamps)"}; + ImGui::Combo("contact shadows (gfx.contactshadows)", &Global.gfx_contactshadows, contactlevels, IM_ARRAYSIZE(contactlevels)); + ImGui::SliderFloat("length of their rays, m (gfx.contactshadows.length)", &Global.gfx_contactshadows_length, 0.05f, 5.f, "%.2f", ImGuiSliderFlags_Logarithmic); + ImGui::SliderFloat("distance they reach, m (gfx.contactshadows.range)", &Global.gfx_contactshadows_range, 1.f, 1000.f, "%.0f", ImGuiSliderFlags_Logarithmic); } ImGui::End(); @@ -882,6 +897,8 @@ void opengl46_renderer::draw_debug_ui() ImGui::Checkbox("draw the depth of the instances before their colour (gfx.depthprepass)", &Global.gfx_depthprepass); } ImGui::Checkbox("draw the depth of the whole scene before its colour (gfx.depthprepass.scene)", &Global.gfx_depthprepass_scene); + if (Global.gfx_contactshadows > 0) + ImGui::TextUnformatted("(drawn anyway for the contact shadows, gfx.contactshadows)"); auto const info{m_instancecull.info()}; if (!info.empty()) ImGui::TextUnformatted(info.c_str() + 1); @@ -1124,14 +1141,19 @@ void opengl46_renderer::Render_pass(viewport_config &vp, rendermode const Mode) if( Global.gfx_shadowmap_enabled ) setup_shadow_bind_map(); - // the depth of the whole opaque scene before its colour + // the depth of the whole opaque scene before its colour, which the contact shadows need m_scenedepthdrawn = false; m_indirectcolourpending = false; - if (Global.gfx_depthprepass_scene && vp.main && (false == Global.bWireFrame)) + auto const contactshadows{Contact_Shadows_Wanted(vp)}; + if ((Global.gfx_depthprepass_scene || contactshadows) && vp.main && (false == Global.bWireFrame)) { glDebug("render scene depth"); Render_Scene_Depth(future); + if (contactshadows) + Setup_Contact_Shadows(vp); } + if (vp.main) + m_contactshadowslast = m_contactshadows; // opaque parts... setup_drawing(false); @@ -1203,6 +1225,15 @@ void opengl46_renderer::Render_pass(viewport_config &vp, rendermode const Mode) } } + if (m_contactshadows) + { + // the depth they read is of this view only + m_contactshadows = false; + scene_ubs.contact_shadow_params = glm::vec4{0.f}; + scene_ubs.contact_shadow_lamps = glm::vec4{0.f}; + scene_ubo->update(scene_ubs); + } + Timer::subsystem.gfx_color.stop(); // store draw stats @@ -3957,6 +3988,46 @@ bool opengl46_renderer::Scene_Depth_Draws( gfx::geometry_handle const &Geometry return ( ( type == GL_TRIANGLES ) || ( type == GL_TRIANGLE_STRIP ) || ( type == GL_TRIANGLE_FAN ) ); } +// the contact shadows read the depth of the scene drawn before the colour, which the main view has, and work out the distance +// of a surface from its depth the way a perspective projection with the far plane at infinity gives it (depth = near / w): +// not in the orthographic view of the editor +bool opengl46_renderer::Contact_Shadows_Wanted( viewport_config const &Viewport ) const +{ + if( ( Global.gfx_contactshadows <= 0 ) || ( false == Viewport.main ) || Global.bWireFrame || Global.gfx_skippipeline || ( Viewport.msaa_fb == nullptr ) ) { + return false; + } + auto const &projection { scene_ubs.projection }; + return ( projection[ 2 ][ 3 ] != 0.f ) && ( projection[ 2 ][ 2 ] == 0.f ) && ( projection[ 3 ][ 2 ] > 0.f ); +} + +void opengl46_renderer::Setup_Contact_Shadows( viewport_config &Viewport ) +{ + if( false == Resolved_Depth_Target( Viewport ) ) { return; } + // making the target binds it + Viewport.msaa_fb->bind(); + + m_contactshadowstimer.begin(); + // the depth is resolved apart from the buffer the colour pass draws to, which it tests and writes. the scissor test would limit the blit + auto const scissor { ::glIsEnabled( GL_SCISSOR_TEST ) == GL_TRUE }; + if( scissor ) { ::glDisable( GL_SCISSOR_TEST ); } + ::glBlitNamedFramebuffer( + *Viewport.msaa_fb, *Viewport.clusters_depth_fb, + 0, 0, Viewport.width, Viewport.height, 0, 0, Viewport.width, Viewport.height, + GL_DEPTH_BUFFER_BIT, GL_NEAREST ); + if( scissor ) { ::glEnable( GL_SCISSOR_TEST ); } + ::glBindTextureUnit( gl::SCENE_DEPTH_TEX, Viewport.clusters_depth_tex->id ); + m_contactshadowstimer.end(); + + // steps of the ray towards the sun, of the rays towards lamps and headlights, and how many of those a pixel traces + struct contact_level { float sunsteps; float lampsteps; float lamps; }; + static contact_level const levels[] { { 8.f, 0.f, 0.f }, { 12.f, 8.f, 2.f }, { 16.f, 12.f, 4.f } }; + auto const &level { levels[ std::clamp( Global.gfx_contactshadows, 1, 3 ) - 1 ] }; + scene_ubs.contact_shadow_params = glm::vec4{ Global.gfx_contactshadows_length, Global.gfx_contactshadows_range, level.sunsteps, level.lampsteps }; + scene_ubs.contact_shadow_lamps = glm::vec4{ level.lamps, 0.f, 0.f, 0.f }; + scene_ubo->update( scene_ubs ); + m_contactshadows = true; +} + // Renders the per-track sleeper instances (TTrack::m_sleeper_local_transforms) using the // existing GPU-instanced submodel pipeline. The track owns a vector of pre-baked // local-space matrices; we compose each with `view * translate(track_origin - camera)` @@ -7063,6 +7134,31 @@ void opengl46_renderer::Render_ambient_occlusion(viewport_config &vp, gl::frameb m_ao.apply(Mode, *vp.main_texd, *vp.ssao_blur_tex, Target, projection); } +bool opengl46_renderer::Resolved_Depth_Target(viewport_config &vp) +{ + if (vp.clusters_depth_fb) + return true; + + auto texture{std::make_unique()}; + texture->alloc_rendertarget(Global.gfx_format_depth, GL_DEPTH_COMPONENT, vp.width, vp.height, 1, 1, GL_CLAMP_TO_EDGE); + // the values themselves are read, texel by texel + glTextureParameteri(texture->id, GL_TEXTURE_MIN_FILTER, GL_NEAREST); + glTextureParameteri(texture->id, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + + auto framebuffer{std::make_unique()}; + framebuffer->attach(*texture, GL_DEPTH_ATTACHMENT); + // depth only + framebuffer->setup_drawing(0); + if (!framebuffer->is_complete()) + { + ErrorLog("resolved depth framebuffer setup failed"); + return false; + } + vp.clusters_depth_tex = std::move(texture); + vp.clusters_depth_fb = std::move(framebuffer); + return true; +} + void opengl46_renderer::Render_cluster_heatmap(viewport_config &vp, gl::framebuffer *Target, glm::ivec2 const &Targetsize) { glDebug("Render_cluster_heatmap"); @@ -7070,26 +7166,10 @@ void opengl46_renderer::Render_cluster_heatmap(viewport_config &vp, gl::framebuf if (!vp.msaa_fb) return; - if (!vp.clusters_depth_fb) + if (!Resolved_Depth_Target(vp)) { - auto texture{std::make_unique()}; - texture->alloc_rendertarget(Global.gfx_format_depth, GL_DEPTH_COMPONENT, vp.width, vp.height, 1, 1, GL_CLAMP_TO_EDGE); - // the values themselves are read, texel by texel - glTextureParameteri(texture->id, GL_TEXTURE_MIN_FILTER, GL_NEAREST); - glTextureParameteri(texture->id, GL_TEXTURE_MAG_FILTER, GL_NEAREST); - - auto framebuffer{std::make_unique()}; - framebuffer->attach(*texture, GL_DEPTH_ATTACHMENT); - // depth only - framebuffer->setup_drawing(0); - if (!framebuffer->is_complete()) - { - ErrorLog("cluster heatmap framebuffer setup failed"); - Global.gfx_lights_clusterheatmap = false; - return; - } - vp.clusters_depth_tex = std::move(texture); - vp.clusters_depth_fb = std::move(framebuffer); + Global.gfx_lights_clusterheatmap = false; + return; } // the depth of the scene is still in the msaa buffer, the post processing worked on its colour diff --git a/rendering/opengl46/opengl46renderer.h b/rendering/opengl46/opengl46renderer.h index 6cb0c0f1..c277ce3f 100644 --- a/rendering/opengl46/opengl46renderer.h +++ b/rendering/opengl46/opengl46renderer.h @@ -277,7 +277,8 @@ class opengl46_renderer : public gfx_renderer { // light cluster grid of the viewport std::unique_ptr clusters; - // depth of the finished scene resolved from the msaa buffer, for the cluster heatmap; made when it is first needed + // depth of the scene resolved from the msaa buffer (Resolved_Depth_Target()): of the finished scene for the cluster heatmap, + // and of the depth pass of the scene for the contact shadows, read while the colour is drawn. made when it is first needed std::unique_ptr clusters_depth_fb; std::unique_ptr clusters_depth_tex; @@ -362,6 +363,12 @@ class opengl46_renderer : public gfx_renderer { // walks the scene a second time, Bind_Material() binds the programs of the depth prepass and draw() leaves out what they // can't draw. Future: the motion matrix the colour pass draws the region with void Render_Scene_Depth( glm::mat4 const &Future ); + // whether the colour pass of the viewport gets contact shadows, which need the depth pass of the scene + bool Contact_Shadows_Wanted( viewport_config const &Viewport ) const; + // resolves the depth drawn by Render_Scene_Depth() for the contact shadows and sets them up for the colour pass + void Setup_Contact_Shadows( viewport_config &Viewport ); + // makes the target of the resolved depth of the viewport if it isn't there yet; false if it can't be made + bool Resolved_Depth_Target( viewport_config &Viewport ); // whether the depth pass of the whole scene draws the material bound last (Bind_Material()) and the geometry bool Scene_Depth_Draws( gfx::geometry_handle const &Geometry ) const; bool Render(TDynamicObject *Dynamic); @@ -553,6 +560,10 @@ class opengl46_renderer : public gfx_renderer { debug_stats m_scenedepthstats; // of its last run double m_scenedepthcputime { 0.0 }; // ms gl::gpu_timer m_scenedepthtimer; + // contact shadows: set up for the colour pass under way, and whether the last main view had them + bool m_contactshadows { false }; + bool m_contactshadowslast { false }; + gl::gpu_timer m_contactshadowstimer; // resolve of the depth renderpass_config m_colorpass; // parametrs of most recent color pass std::array m_shadowpass; // parametrs of most recent shadowmap pass for each of csm stages std::vector m_pickcontrolsitems; diff --git a/shaders/light_common.glsl b/shaders/light_common.glsl index ccf8d785..913f6ffc 100644 --- a/shaders/light_common.glsl +++ b/shaders/light_common.glsl @@ -8,6 +8,8 @@ layout(binding = LOCAL_SHADOW_TEX) uniform samplerCubeArrayShadow local_shadowma layout(binding = LOCAL_SHADOW_LAMPS_TEX) uniform samplerCubeArrayShadow local_shadowmap_lamps; // shadow maps of the headlights of vehicles, see gl::HEADLIGHT_SHADOW_TEX and gl::scene_ubs::headlight_shadow layout(binding = HEADLIGHT_SHADOW_TEX) uniform sampler2DArrayShadow headlight_shadowmap; +// depth of the opaque scene drawn before the colour, for the contact shadows (gl::scene_ubs::contact_shadow_params) +layout(binding = SCENE_DEPTH_TEX) uniform sampler2D scene_depth; #include #include @@ -42,7 +44,83 @@ float length2(vec3 v) return dot(v, v); } +// how many more lamps and headlights the fragment traces contact shadows to, see add_headlights() +int contact_shadow_budget = 0; + +// contact shadow of a light in direction Light_dir (view space, towards the light), 0..1: whether the depth of the scene drawn +// before the colour (scene_depth) holds a surface between the fragment and a point up to Length metres towards the light. it +// catches what the shadow maps are too coarse for (rails on the sleepers, bolts, edges of panels, where a thing stands on the +// ground), and things too small or near for them. only what is in the depth casts it, and only within the image. +// the ray starts a pixel and a half off the visible side of the surface: the depth there, sampled anywhere within a pixel of +// the projected point, then stays behind the ray on a flat surface, so it doesn't shadow itself. a surface nearer than the +// ray by more than the thickness is taken for something in front of the ray, not something it goes through. +// the shadow fades over the last part of the ray (it doesn't end with an edge) and over the last quarter of the range +float contact_shadow(vec3 light_dir, float raylength, float steps) +{ + float range = contact_shadow_params.y; + vec3 position = f_pos.xyz; + float fade = 1.0 - smoothstep(range * 0.75, range, length(position)); + if ((fade <= 0.0) || (raylength <= 0.0) || (steps < 1.0)) + return 0.0; + vec3 normal = normalize(f_normal); + if (dot(normal, position) > 0.0) + normal = -normal; + if (dot(normal, light_dir) <= 0.0) + return 0.0; + vec2 size = vec2(textureSize(scene_depth, 0)); + // the projection is perspective with the depth n / w (reverse z, infinite far plane): w is the distance along the axis + // of the view, and a depth d is a surface at n / d + float near = projection[3][2]; + float distance = (projection * vec4(position, 1.0)).w; + float pixel = 2.0 * distance / (abs(projection[1][1]) * size.y); + vec4 start = projection * vec4(position + normal * (pixel * 1.5), 1.0); + vec4 ray = projection * vec4(light_dir * raylength, 0.0); + float thickness = 0.3 + pixel * 4.0; + float bias = pixel * 0.5; + // a different start along the ray in neighbouring pixels: the steps give noise instead of bands + float jitter = fract(52.9829189 * fract(dot(gl_FragCoord.xy, vec2(0.06711056, 0.00583715)))); + for (float i = 0.0; i < steps; i += 1.0) + { + float t = (i + jitter) / steps; + vec4 clip = start + ray * t; + if (clip.w <= near) + break; + vec2 coords = (clip.xy / clip.w) * 0.5 + 0.5; + if (any(lessThan(coords, vec2(0.0))) || any(greaterThan(coords, vec2(1.0)))) + break; + float depth = texelFetch(scene_depth, min(ivec2(coords * size), ivec2(size) - 1), 0).r; + // nothing drawn there (the sky) + if (depth <= 0.0) + continue; + float nearer = clip.w - near / depth; + if ((nearer > bias) && (nearer < thickness)) + return fade * (1.0 - smoothstep(0.6, 1.0, t)); + } + return 0.0; +} + +// contact shadow of a lamp or headlight Distance metres away, whose light reaches the fragment with Strength, for the first +// few lamps of a fragment (contact_shadow_lamps.x) which light it noticeably. the ray goes at most half way to the light, +// so the lamp doesn't shadow with its own fitting +float contact_shadow_lamp(vec3 light_dir, float distance, float strength) +{ + if ((contact_shadow_budget <= 0) || (strength < 0.005)) + return 0.0; + --contact_shadow_budget; + return contact_shadow(light_dir, min(contact_shadow_params.x, distance * 0.5), contact_shadow_params.w); +} + +// share of the sun (the moon) held back before the fragment by the shadow map and the contact shadow, 0..1 +float calc_shadow_map(); float calc_shadow() +{ + float shadow = calc_shadow_map(); + if ((contact_shadow_params.x > 0.0) && (shadow < 1.0)) + shadow = max(shadow, contact_shadow(normalize(-lights[0].dir), contact_shadow_params.x, contact_shadow_params.z)); + return shadow; +} + +float calc_shadow_map() { #if SHADOWMAP_ENABLED float distance = dot(f_pos.xyz, f_pos.xyz); @@ -282,6 +360,9 @@ vec2 calc_headlights(light_s light, vec3 fragnormal, vec3 dpdx, vec3 dpdy) // headlights with a shadow map: what is in the way holds the beam back if ((light.headlight_weights.w > 0.5) && (lightintensity > 0.0) && (part.x > 0.0)) lightintensity *= 1.0 - headlight_beam_shadow(uint(light.headlight_weights.w - 0.5), fragnormal, light_dir); + if ((contact_shadow_budget > 0) && (lightintensity > 0.0) && (part.x > 0.0)) + lightintensity *= 1.0 - contact_shadow_lamp(light_dir, distance, + part.x * atten * lightintensity * light.intensity * max(light.color.r, max(light.color.g, light.color.b))); return part * atten * lightintensity; } @@ -352,6 +433,9 @@ vec2 calc_local_light(light_s light, vec3 fragnormal) atten *= smoothstep(light.out_cutoff, light.in_cutoff, dot(-light_dir, normalize(light.dir))); if ((atten > 0.0) && (light.headlight_weights.x >= 0.0) && (dot(fragnormal, light_dir) > 0.0)) atten *= 1.0 - local_light_shadow(light, fragnormal); + if ((atten > 0.0) && (contact_shadow_budget > 0)) + atten *= 1.0 - contact_shadow_lamp(light_dir, sqrt(distance2), + atten * max(dot(fragnormal, light_dir), 0.0) * light.intensity * max(light.color.r, max(light.color.g, light.color.b))); if (atten <= 0.0) return vec2(0.0); vec2 part = calc_light(light_dir, fragnormal); @@ -378,6 +462,7 @@ vec3 add_headlights(vec3 fragcolor, vec3 fragnormal) // worked out before the loops, which the fragments of a quad do not leave together vec3 dpdx = dFdx(f_pos.xyz); vec3 dpdy = dFdy(f_pos.xyz); + contact_shadow_budget = (contact_shadow_params.x > 0.0) ? int(contact_shadow_lamps.x) : 0; if (lights_clustered != 0U) { diff --git a/utilities/Globals.cpp b/utilities/Globals.cpp index c8ca9d26..daab635b 100644 --- a/utilities/Globals.cpp +++ b/utilities/Globals.cpp @@ -1655,6 +1655,18 @@ global_settings::ConfigParse_gfx_sky( cParser &Parser, std::string_view const To Parser >> gfx_lights_shadows_headlights; gfx_lights_shadows_headlights = std::clamp( gfx_lights_shadows_headlights, 0, 4 ); } else if( Token == "gfx.lights.shadows.headlights.range" ) { readvalue( gfx_lights_shadows_headlights_range, 10.f, 1000.f ); } + else if( Token == "gfx.contactshadows" ) { + // no/low/medium/high, or the number of the level + Parser.getTokens( 1 ); + std::string level; + Parser >> level; + gfx_contactshadows = + ( level == "low" || level == "1" ) ? 1 : + ( level == "medium" || level == "yes" || level == "true" || level == "2" ) ? 2 : + ( level == "high" || level == "3" ) ? 3 : + 0; } + else if( Token == "gfx.contactshadows.length" ) { readvalue( gfx_contactshadows_length, 0.05f, 5.f ); } + else if( Token == "gfx.contactshadows.range" ) { readvalue( gfx_contactshadows_range, 1.f, 1000.f ); } else if( Token == "gfx.fog" ) { // volumetric (yes) or analytic (no, old): the same weather either way Parser.getTokens( 1 ); @@ -1978,6 +1990,9 @@ global_settings::export_as_text( std::ostream &Output ) const { export_as_text( Output, "gfx.lights.shadows.vehicles.range", gfx_lights_shadows_vehicles_range ); export_as_text( Output, "gfx.lights.shadows.headlights", gfx_lights_shadows_headlights ); export_as_text( Output, "gfx.lights.shadows.headlights.range", gfx_lights_shadows_headlights_range ); + export_as_text( Output, "gfx.contactshadows", std::string{ gfx_contactshadows == 3 ? "high" : gfx_contactshadows == 2 ? "medium" : gfx_contactshadows == 1 ? "low" : "no" } ); + export_as_text( Output, "gfx.contactshadows.length", gfx_contactshadows_length ); + export_as_text( Output, "gfx.contactshadows.range", gfx_contactshadows_range ); export_as_text( Output, "gfx.fog", std::string{ gfx_fog == 1 ? "volumetric" : "analytic" } ); export_as_text( Output, "gfx.fog.quality", std::string{ gfx_fog_quality == 1 ? "low" : gfx_fog_quality == 3 ? "high" : gfx_fog_quality == 4 ? "ultra" : "medium" } ); export_as_text( Output, "gfx.fog.range", gfx_fog_range ); diff --git a/utilities/Globals.h b/utilities/Globals.h index 35b815f1..ca82b801 100644 --- a/utilities/Globals.h +++ b/utilities/Globals.h @@ -370,6 +370,12 @@ struct global_settings { // cast them, metres int gfx_lights_shadows_headlights { 2 }; float gfx_lights_shadows_headlights_range { 250.f }; + // (opengl 4.6) contact shadows: short rays through the depth of the scene drawn before its colour (which they turn on, see + // gfx_depthprepass_scene) towards the sun and the nearest lamps. 0 none, 1 low (the sun), 2 medium (and 2 lamps), 3 high + // (and 4 lamps, more steps); the length of the rays and the distance from the camera within which they are traced, metres + int gfx_contactshadows { 0 }; + float gfx_contactshadows_length { 0.5f }; + float gfx_contactshadows_range { 50.f }; // weather of the opengl 4.6 renderer (debug window "Weather"); the weather itself comes from the scenery, see // environment/weather.h. fog: 1 volumetric (a volume over the view with the shafts of the sun, the shadows of the clouds and // the light of lamps and headlights, worked out along the ray past it), 0 analytic (the same fog along the ray only, the way