17
0
mirror of https://github.com/MaSzyna-EU07/maszyna.git synced 2026-10-17 03:59:19 +02:00

Add screen-space contact shadows for the sun, lamps and headlights

This commit is contained in:
2026-10-17 03:11:47 +02:00
parent c4da5c9867
commit 27e5c07837
8 changed files with 268 additions and 25 deletions

View File

@@ -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

View File

@@ -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

View File

@@ -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;

View File

@@ -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<double>( *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<double>( *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<GLsizeiptr>( 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<opengl_texture>()};
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<gl::framebuffer>()};
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<opengl_texture>()};
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<gl::framebuffer>()};
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

View File

@@ -277,7 +277,8 @@ class opengl46_renderer : public gfx_renderer {
// light cluster grid of the viewport
std::unique_ptr<gl::cluster_grid> 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<gl::framebuffer> clusters_depth_fb;
std::unique_ptr<opengl_texture> 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<renderpass_config, 3> m_shadowpass; // parametrs of most recent shadowmap pass for each of csm stages
std::vector<TSubModel const *> m_pickcontrolsitems;

View File

@@ -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 <envmapping.glsl>
#include <conversion.glsl>
@@ -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)
{

View File

@@ -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 );

View File

@@ -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