mirror of
https://github.com/MaSzyna-EU07/maszyna.git
synced 2026-11-06 17:28:36 +01: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` | `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 |
|
| `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
|
### Weather
|
||||||
|
|
||||||
The clouds, the fog and the stars are drawn from the weather of the simulation (a `weather` section of the scenery, or its
|
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.
|
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)
|
- 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).
|
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
|
- 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
|
`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
|
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_DETAIL_TEX " + std::to_string(CLOUD_DETAIL_TEX) + "\n" +
|
||||||
"#define CLOUD_COVERAGE_TEX " + std::to_string(CLOUD_COVERAGE_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 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_UBO_BINDING " + std::to_string(WEATHER_UBO_BINDING) + "\n" +
|
||||||
"#define WEATHER_SCRATCH_SSBO_BINDING " + std::to_string(WEATHER_SCRATCH_SSBO_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" +
|
"#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
|
// shadow maps of the headlights, see gl::scene_ubs
|
||||||
mat4 headlight_shadow[MAX_HEADLIGHT_SHADOWS];
|
mat4 headlight_shadow[MAX_HEADLIGHT_SHADOWS];
|
||||||
vec4 headlight_shadow_params[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
|
#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_DETAIL_TEX = MAX_TEXTURES + HELPER_TEXTURES + 8;
|
||||||
const size_t CLOUD_COVERAGE_TEX = MAX_TEXTURES + HELPER_TEXTURES + 9;
|
const size_t CLOUD_COVERAGE_TEX = MAX_TEXTURES + HELPER_TEXTURES + 9;
|
||||||
const size_t CLOUD_HISTORY_TEX = MAX_TEXTURES + HELPER_TEXTURES + 10;
|
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
|
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
|
// 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::mat4 headlight_shadow[MAX_HEADLIGHT_SHADOWS];
|
||||||
glm::vec4 headlight_shadow_params[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;
|
const size_t MAX_PARAMS = 3;
|
||||||
|
|
||||||
|
|||||||
@@ -740,10 +740,14 @@ void opengl46_renderer::SwapBuffers()
|
|||||||
m_debugtimestext += m_exposure.info_times();
|
m_debugtimestext += m_exposure.info_times();
|
||||||
m_debugtimestext += m_ao.info_times();
|
m_debugtimestext += m_ao.info_times();
|
||||||
m_debugtimestext += m_instancecull.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() )
|
if( auto const scenedepthtime = m_scenedepthtimer.result() )
|
||||||
m_debugtimestext += " scene depth: " + to_string( static_cast<double>( *scenedepthtime ) / 1000000.0, 3 ) + " ms\n";
|
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 ) {
|
if( Global.gfx_gpucull == 1 ) {
|
||||||
m_debugtimestext += m_hiz.info_times();
|
m_debugtimestext += m_hiz.info_times();
|
||||||
if( auto const prepasstime = m_prepasstimer.result() )
|
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 )
|
+ " instanced:" + to_string( m_colorpass.draw_stats.instances, 7 ) + " +" + to_string( shadowstats.instances, 7 )
|
||||||
+ " =" + to_string( m_colorpass.draw_stats.instances + 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"
|
+ " (" + 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" :
|
" scene depth: " + std::to_string( m_scenedepthstats.drawcalls ) + " draws, cpu " + to_string( m_scenedepthcputime, 2 ) + " ms\n" :
|
||||||
std::string{} )
|
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, "
|
+ " instance buffer: " + std::to_string( m_instancestream->used() / static_cast<GLsizeiptr>( sizeof( glm::mat4 ) ) ) + " matrices, "
|
||||||
+ std::to_string( m_instancestream->size() / 1024 ) + " KiB\n"
|
+ std::to_string( m_instancestream->size() / 1024 ) + " KiB\n"
|
||||||
+ " model blocks: " + std::to_string( model_ubo->updates() )
|
+ " model blocks: " + std::to_string( model_ubo->updates() )
|
||||||
@@ -853,6 +863,11 @@ void opengl46_renderer::draw_debug_ui()
|
|||||||
m_locallights.draw_debug_ui();
|
m_locallights.draw_debug_ui();
|
||||||
ImGui::Separator();
|
ImGui::Separator();
|
||||||
m_localshadows.draw_debug_ui();
|
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();
|
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 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);
|
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()};
|
auto const info{m_instancecull.info()};
|
||||||
if (!info.empty())
|
if (!info.empty())
|
||||||
ImGui::TextUnformatted(info.c_str() + 1);
|
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 )
|
if( Global.gfx_shadowmap_enabled )
|
||||||
setup_shadow_bind_map();
|
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_scenedepthdrawn = false;
|
||||||
m_indirectcolourpending = 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");
|
glDebug("render scene depth");
|
||||||
Render_Scene_Depth(future);
|
Render_Scene_Depth(future);
|
||||||
|
if (contactshadows)
|
||||||
|
Setup_Contact_Shadows(vp);
|
||||||
}
|
}
|
||||||
|
if (vp.main)
|
||||||
|
m_contactshadowslast = m_contactshadows;
|
||||||
|
|
||||||
// opaque parts...
|
// opaque parts...
|
||||||
setup_drawing(false);
|
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();
|
Timer::subsystem.gfx_color.stop();
|
||||||
|
|
||||||
// store draw stats
|
// 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 ) );
|
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
|
// 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
|
// 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)`
|
// 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);
|
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)
|
void opengl46_renderer::Render_cluster_heatmap(viewport_config &vp, gl::framebuffer *Target, glm::ivec2 const &Targetsize)
|
||||||
{
|
{
|
||||||
glDebug("Render_cluster_heatmap");
|
glDebug("Render_cluster_heatmap");
|
||||||
@@ -7070,26 +7166,10 @@ void opengl46_renderer::Render_cluster_heatmap(viewport_config &vp, gl::framebuf
|
|||||||
if (!vp.msaa_fb)
|
if (!vp.msaa_fb)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if (!vp.clusters_depth_fb)
|
if (!Resolved_Depth_Target(vp))
|
||||||
{
|
{
|
||||||
auto texture{std::make_unique<opengl_texture>()};
|
Global.gfx_lights_clusterheatmap = false;
|
||||||
texture->alloc_rendertarget(Global.gfx_format_depth, GL_DEPTH_COMPONENT, vp.width, vp.height, 1, 1, GL_CLAMP_TO_EDGE);
|
return;
|
||||||
// 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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// the depth of the scene is still in the msaa buffer, the post processing worked on its colour
|
// 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
|
// light cluster grid of the viewport
|
||||||
std::unique_ptr<gl::cluster_grid> clusters;
|
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<gl::framebuffer> clusters_depth_fb;
|
||||||
std::unique_ptr<opengl_texture> clusters_depth_tex;
|
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
|
// 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
|
// can't draw. Future: the motion matrix the colour pass draws the region with
|
||||||
void Render_Scene_Depth( glm::mat4 const &Future );
|
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
|
// 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 Scene_Depth_Draws( gfx::geometry_handle const &Geometry ) const;
|
||||||
bool Render(TDynamicObject *Dynamic);
|
bool Render(TDynamicObject *Dynamic);
|
||||||
@@ -553,6 +560,10 @@ class opengl46_renderer : public gfx_renderer {
|
|||||||
debug_stats m_scenedepthstats; // of its last run
|
debug_stats m_scenedepthstats; // of its last run
|
||||||
double m_scenedepthcputime { 0.0 }; // ms
|
double m_scenedepthcputime { 0.0 }; // ms
|
||||||
gl::gpu_timer m_scenedepthtimer;
|
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
|
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::array<renderpass_config, 3> m_shadowpass; // parametrs of most recent shadowmap pass for each of csm stages
|
||||||
std::vector<TSubModel const *> m_pickcontrolsitems;
|
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;
|
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
|
// 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;
|
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 <envmapping.glsl>
|
||||||
#include <conversion.glsl>
|
#include <conversion.glsl>
|
||||||
@@ -42,7 +44,83 @@ float length2(vec3 v)
|
|||||||
return dot(v, 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 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
|
#if SHADOWMAP_ENABLED
|
||||||
float distance = dot(f_pos.xyz, f_pos.xyz);
|
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
|
// 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))
|
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);
|
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;
|
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)));
|
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))
|
if ((atten > 0.0) && (light.headlight_weights.x >= 0.0) && (dot(fragnormal, light_dir) > 0.0))
|
||||||
atten *= 1.0 - local_light_shadow(light, fragnormal);
|
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)
|
if (atten <= 0.0)
|
||||||
return vec2(0.0);
|
return vec2(0.0);
|
||||||
vec2 part = calc_light(light_dir, fragnormal);
|
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
|
// worked out before the loops, which the fragments of a quad do not leave together
|
||||||
vec3 dpdx = dFdx(f_pos.xyz);
|
vec3 dpdx = dFdx(f_pos.xyz);
|
||||||
vec3 dpdy = dFdy(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)
|
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;
|
Parser >> gfx_lights_shadows_headlights;
|
||||||
gfx_lights_shadows_headlights = std::clamp( gfx_lights_shadows_headlights, 0, 4 ); }
|
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.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" ) {
|
else if( Token == "gfx.fog" ) {
|
||||||
// volumetric (yes) or analytic (no, old): the same weather either way
|
// volumetric (yes) or analytic (no, old): the same weather either way
|
||||||
Parser.getTokens( 1 );
|
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.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", gfx_lights_shadows_headlights );
|
||||||
export_as_text( Output, "gfx.lights.shadows.headlights.range", gfx_lights_shadows_headlights_range );
|
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", 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.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 );
|
export_as_text( Output, "gfx.fog.range", gfx_fog_range );
|
||||||
|
|||||||
@@ -370,6 +370,12 @@ struct global_settings {
|
|||||||
// cast them, metres
|
// cast them, metres
|
||||||
int gfx_lights_shadows_headlights { 2 };
|
int gfx_lights_shadows_headlights { 2 };
|
||||||
float gfx_lights_shadows_headlights_range { 250.f };
|
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
|
// 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
|
// 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
|
// 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