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:
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
11
gl/ubo.h
11
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;
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
@@ -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 );
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user