17
0
mirror of https://github.com/MaSzyna-EU07/maszyna.git synced 2026-10-26 02:12:27 +01:00

Add gfx.antialiasing (off, msaax2/x4/x8, taa) with temporal anti-aliasing in the GL 4.6 and Vulkan renderers

This commit is contained in:
2026-10-25 05:17:41 +01:00
parent c48c58776a
commit 252b9373ea
20 changed files with 838 additions and 35 deletions

View File

@@ -321,6 +321,7 @@ set(SOURCES
"rendering/common/skylight.cpp"
"rendering/common/weatherstate.cpp"
"rendering/common/exposure.cpp"
"rendering/common/temporalaa.cpp"
"gl/buffer.cpp"
"gl/fence.cpp"
"gl/pbo.cpp"

View File

@@ -7,6 +7,7 @@ struct _global
bool gfx_skippipeline;
bool gfx_extraeffects;
bool gfx_shadergamma;
bool gfx_motion_vectors() const { return gfx_postfx_motionblur_enabled; }
};
extern _global Global;

View File

@@ -80,6 +80,20 @@ existed.
The automatic exposure and the bloom need the post processing pipeline (`gfx.skippipeline no`, the default).
Model previews of the editor use none of these.
### Anti-aliasing
| Entry | Default | Meaning |
|---|---|---|
| `gfx.antialiasing off/msaax2/msaax4/msaax8/taa` | `msaax4` | `msaax2`..`msaax8`: multisampling of the colour pass. `taa`: temporal anti-aliasing of the main view: one sample per pixel, the projection moved by a part of a pixel every frame (8 offsets) and the frame blended into the history of the last frames along the velocities of the pixels. Also in the Vulkan renderer. Read at the start; the older `multisampling 0..4` still works, the entry later in the file wins |
TAA needs the post processing pipeline (`gfx.skippipeline no`); extra viewports and the eyes of the VR get no
anti-aliasing with it. The velocities are drawn with it whether the motion blur is on or not; with both on the
motion blur smears the image after the TAA, and it also blurs the turning of the camera. Thin lines across the
pixels (rails and wires far away) are locked in the history for 8 frames on the world standing still farther
than 20..40 m, so the frames whose offset misses them don't make them flicker; on near or fast moving pixels the
usual check of the history against the colours around the pixel applies. Further anti-aliasing modes (FSR, DLSS) are meant to be added to
the same entry.
### Ambient occlusion
Darkening of creases, corners and the ground under objects, worked out from the depth of the image (screen space)

View File

@@ -100,7 +100,7 @@ std::string gl::glsl_settings_defines()
return
"#define SHADOWMAP_ENABLED_VALUE " + value(Global.gfx_shadowmap_enabled) + "\n"
"#define ENVMAP_ENABLED_VALUE " + value(Global.gfx_envmap_enabled) + "\n"
"#define MOTIONBLUR_ENABLED_VALUE " + value(Global.gfx_postfx_motionblur_enabled) + "\n"
"#define MOTIONBLUR_ENABLED_VALUE " + value(Global.gfx_motion_vectors()) + "\n"
"#define POSTFX_ENABLED_VALUE " + value(!Global.gfx_skippipeline) + "\n"
"#define EXTRAEFFECTS_ENABLED_VALUE " + value(Global.gfx_extraeffects) + "\n"
"#define OUTPUT_GAMMA_VALUE " + value(Global.gfx_shadergamma) + "\n";
@@ -112,7 +112,7 @@ std::array<GLuint, 6> gl::glsl_settings_values()
return {
static_cast<GLuint>(Global.gfx_shadowmap_enabled),
static_cast<GLuint>(Global.gfx_envmap_enabled),
static_cast<GLuint>(Global.gfx_postfx_motionblur_enabled),
static_cast<GLuint>(Global.gfx_motion_vectors()),
static_cast<GLuint>(!Global.gfx_skippipeline),
static_cast<GLuint>(Global.gfx_extraeffects),
static_cast<GLuint>(Global.gfx_shadergamma) };

View File

@@ -1640,7 +1640,7 @@ bool scene_walk::Render(TDynamicObject *Dynamic)
glm::mat4 future_stack = model_ubs.future;
glm::mat4 mv = OpenGLMatrices.data(GL_MODELVIEW);
model_ubs.future *= glm::translate(mv, glm::vec3(Dynamic->get_future_movement())) * glm::inverse(mv);
model_ubs.future *= glm::translate(mv, glm::vec3(m_motionhistory ? -Dynamic->get_future_movement() : Dynamic->get_future_movement())) * glm::inverse(mv);
// the velocity it leaves in the colour pass keeps the motion blur on (Render_pass())
if ((m_renderpass.draw_mode == rendermode::color) && (Dynamic->get_future_movement() != glm::dvec3{0.0}))
m_vehiclesmoving = true;
@@ -1901,7 +1901,7 @@ void scene_walk::Render(TSubModel *Submodel)
Submodel->RaAnimation(Submodel->b_aAnim);
glm::mat4 mv = OpenGLMatrices.data(GL_MODELVIEW);
model_ubs.future *= (mv * Submodel->future_transform) * glm::inverse(mv);
model_ubs.future *= (mv * (m_motionhistory ? glm::inverse(Submodel->future_transform) : Submodel->future_transform)) * glm::inverse(mv);
// the placement may be different in the next frame: the walk isn't kept (Record_Indirect_Draws())
m_indirectchanging = true;
}
@@ -2716,7 +2716,7 @@ bool scene_walk::Render_Alpha(TDynamicObject *Dynamic)
glm::mat4 future_stack = model_ubs.future;
glm::mat4 mv = OpenGLMatrices.data(GL_MODELVIEW);
model_ubs.future *= glm::translate(mv, glm::vec3(Dynamic->get_future_movement())) * glm::inverse(mv);
model_ubs.future *= glm::translate(mv, glm::vec3(m_motionhistory ? -Dynamic->get_future_movement() : Dynamic->get_future_movement())) * glm::inverse(mv);
// the velocity it leaves in the colour pass keeps the motion blur on (Render_pass())
if ((m_renderpass.draw_mode == rendermode::color) && (Dynamic->get_future_movement() != glm::dvec3{0.0}))
m_vehiclesmoving = true;
@@ -2846,7 +2846,7 @@ void scene_walk::Render_Alpha(TSubModel *Submodel)
Submodel->RaAnimation(Submodel->b_aAnim);
glm::mat4 mv = OpenGLMatrices.data(GL_MODELVIEW);
model_ubs.future *= (mv * Submodel->future_transform) * glm::inverse(mv);
model_ubs.future *= (mv * (m_motionhistory ? glm::inverse(Submodel->future_transform) : Submodel->future_transform)) * glm::inverse(mv);
}
}

View File

@@ -502,6 +502,12 @@ class scene_walk
std::vector<gl::instance_draw_command> m_indirectcommandsordered;
// a vehicle drawn in the colour pass of this frame moved (motion blur)
bool m_vehiclesmoving{false};
// the velocities point to the last frame (the temporal anti-aliasing, gfx::temporal_aa) instead of the next one (the motion
// blur alone): the moving vehicles and the turning wheels add their motion to the future of the draws backwards
bool m_motionhistory{false};
// the future of the draws of the cab and the interiors of its vehicle: the identity (they move with the camera), or the matrix
// of their view of the last frame (gfx::temporal_aa::cab_future())
glm::mat4 m_cabfuture{1.f};
// set while a preview is drawn: submodels faded out entirely are skipped, instead of being drawn invisible over the stencil
bool m_previewing{false};
// the last lighting of the pass placed the cab lamp among the lights: the cab is lit by it, not by an addition to the ambient light

View File

@@ -0,0 +1,89 @@
/*
This Source Code Form is subject to the
terms of the Mozilla Public License, v.
2.0. If a copy of the MPL was not
distributed with this file, You can
obtain one at
http://mozilla.org/MPL/2.0/.
*/
#include "stdafx.h"
#include "rendering/common/temporalaa.h"
#include "vehicle/DynObj.h"
namespace
{
// the offsets go round this many frames: enough to cover a pixel evenly, few enough for the history to hold all of them
constexpr std::uint32_t JITTER_PHASES{8};
// the camera moved farther than this from one frame to the next (another camera, another vehicle, a jump): the history is dropped
constexpr double CAMERA_JUMP{50.0};
// the halton sequence: the index written in the base, mirrored behind the point
float halton(std::uint32_t Index, std::uint32_t const Base)
{
auto fraction{1.f};
auto result{0.f};
while (Index > 0)
{
fraction /= static_cast<float>(Base);
result += fraction * static_cast<float>(Index % Base);
Index /= Base;
}
return result;
}
} // namespace
glm::mat4 gfx::temporal_aa::begin_frame(glm::mat4 const &Projection, glm::dmat3 const &View, glm::dvec3 const &Camera, TDynamicObject const *Cab, glm::ivec2 const &Size)
{
m_phase = (m_phase + 1) % JITTER_PHASES;
// the first element of the sequence is 0 in every base; from the second one on they are spread over the pixel
m_jitter = glm::vec2{halton(m_phase + 1, 2), halton(m_phase + 1, 3)} - 0.5f;
glm::dvec2 const size{static_cast<double>(std::max(Size.x, 1)), static_cast<double>(std::max(Size.y, 1))};
// the clip space is two units across the image
auto const offset{glm::translate(glm::dmat4{1.0}, glm::dvec3{2.0 * glm::dvec2{m_jitter} / size, 0.0})};
frame current;
current.projection = glm::dmat4{Projection};
current.view = View;
current.camera = Camera;
current.vehicle = Cab;
if (Cab != nullptr)
{
// as Render_cab() places it
current.cab = glm::translate(glm::dmat4{1.0}, Cab->GetPosition() - Camera) * Cab->mMatrix;
}
current.size = Size;
current.valid = true;
auto const projection{offset * current.projection};
m_historyvalid = m_previous.valid && (m_previous.size == Size) && (m_previous.vehicle == Cab) && (glm::length(Camera - m_previous.camera) < CAMERA_JUMP);
if (m_historyvalid)
{
auto const inverseprojection{glm::inverse(projection)};
// the view of this frame to the world around the camera, the world around the camera of the last frame, its view
auto const toworld{glm::dmat4{glm::inverse(current.view)}};
auto const world{glm::dmat4{m_previous.view} * glm::translate(glm::dmat4{1.0}, Camera - m_previous.camera) * toworld};
// the cab goes with its vehicle: the view of this frame to the cab, to the world around the camera of the last frame
auto const cab{glm::dmat4{m_previous.view} * m_previous.cab * glm::inverse(current.cab) * toworld};
// the shaders project the future with the projection of this frame (the velocity is the place in the future less the place
// now, both with the offset of this frame): the projection of the last frame with the offset of this one is put in front of
// it in the future. with the projection the same in both frames this is the identity, and the velocity holds just the motion
auto const previous{offset * m_previous.projection};
auto const projectionfix{inverseprojection * previous};
m_worldfuture = glm::mat4{projectionfix * world};
m_cabfuture = glm::mat4{projectionfix * cab};
m_reprojection = glm::mat4{previous * world * inverseprojection};
}
else
{
m_worldfuture = glm::mat4{1.f};
m_cabfuture = glm::mat4{1.f};
m_reprojection = glm::mat4{1.f};
}
m_target = 1 - m_target;
m_previous = current;
return glm::mat4{projection};
}

View File

@@ -0,0 +1,94 @@
/*
This Source Code Form is subject to the
terms of the Mozilla Public License, v.
2.0. If a copy of the MPL was not
distributed with this file, You can
obtain one at
http://mozilla.org/MPL/2.0/.
*/
// the temporal anti-aliasing of the main view (gfx.antialiasing taa), the part shared by the renderers: the sub-pixel offset of the
// projection of every frame, and the matrices which take the pixels of a frame to the place they had in the last one.
//
// with it the velocities of the colour pass point to the last frame: the future of the draws (model_ubs.future) is the matrix of the
// view of the last frame instead of the next one, for the scenery and for the cab apart (the cab moves with the camera), and the
// moving vehicles and wheels take their motion backwards (scene_walk::m_motionhistory). the resolve pass (postfx_taa.frag,
// taa_resolve.glsl) reads the history with the velocities, and takes the pixels of the world standing still (and the sky, which has
// no velocity drawn) to the last frame with reprojection(); on those the thin things (rails, wires) are kept in the history for a few
// frames although the pixel of this frame misses them
#pragma once
#include <cstdint>
#include <glm/glm.hpp>
class TDynamicObject;
namespace gfx
{
class temporal_aa
{
public:
// the frame of the colour pass of the main view: Projection without the offset, View the rotation of the camera (the axes of the
// world to those of the view), Camera its place, Cab the vehicle whose cab is drawn (or null), Size of the images of the pass.
// returns the projection with the offset of the frame
glm::mat4 begin_frame(glm::mat4 const &Projection, glm::dmat3 const &View, glm::dvec3 const &Camera, TDynamicObject const *Cab, glm::ivec2 const &Size);
// the next frame starts the history anew
void reset()
{
m_previous.valid = false;
}
// the frame has a history: the last frame was drawn the same way
bool history_valid() const
{
return m_historyvalid;
}
// model_ubs.future of the scenery, and of the cab with the interiors of its vehicle
glm::mat4 const &world_future() const
{
return m_worldfuture;
}
glm::mat4 const &cab_future() const
{
return m_cabfuture;
}
// the clip space of the pixels of this frame (with their depth) to the clip space of the last frame, for the world standing still
glm::mat4 const &reprojection() const
{
return m_reprojection;
}
// the image of the history written this frame (0 or 1); the other one holds the last frame
int target() const
{
return m_target;
}
// the offset of the frame, in pixels
glm::vec2 jitter() const
{
return m_jitter;
}
private:
struct frame
{
glm::dmat4 projection{1.0}; // without the offset
glm::dmat3 view{1.0};
glm::dvec3 camera{0.0};
glm::dmat4 cab{1.0}; // the cab to the world around the camera
TDynamicObject const *vehicle{nullptr};
glm::ivec2 size{0};
bool valid{false};
};
frame m_previous;
std::uint32_t m_phase{0};
glm::vec2 m_jitter{0.f};
glm::mat4 m_worldfuture{1.f};
glm::mat4 m_cabfuture{1.f};
glm::mat4 m_reprojection{1.f};
int m_target{0};
bool m_historyvalid{false};
};
} // namespace gfx

View File

@@ -234,6 +234,8 @@ bool opengl46_renderer::Init(GLFWwindow *Window)
glEnable(GL_MULTISAMPLE);
m_pfx_motionblur = std::make_unique<gl::postfx>("motionblur");
if (Global.gfx_taa())
m_pfx_taa = std::make_unique<gl::postfx>("taa");
m_pfx_tonemapping = std::make_unique<gl::postfx>("tonemapping");
m_pfx_chromaticaberration = std::make_unique<gl::postfx>( "chromaticaberration" );
@@ -519,7 +521,7 @@ bool opengl46_renderer::init_viewport(viewport_config &vp)
vp.msaa_fb->attach(*vp.msaa_rbc, GL_COLOR_ATTACHMENT0);
vp.msaa_fb->attach(*vp.msaa_rbd, GL_DEPTH_ATTACHMENT);
if (Global.gfx_postfx_chromaticaberration_enabled || Global.gfx_postfx_motionblur_enabled || Global.gfx_postfx_ao != 0)
if (Global.gfx_postfx_chromaticaberration_enabled || Global.gfx_motion_vectors() || Global.gfx_postfx_ao != 0)
{
vp.main_tex = std::make_unique<opengl_texture>();
vp.main_tex->alloc_rendertarget(Global.gfx_format_color, GL_RGB, vp.width, vp.height, 1, 1, GL_CLAMP_TO_EDGE);
@@ -531,7 +533,7 @@ bool opengl46_renderer::init_viewport(viewport_config &vp)
vp.main_fb->attach(*vp.main_tex, GL_COLOR_ATTACHMENT0);
vp.main_fb->attach(*vp.main_texd, GL_DEPTH_ATTACHMENT);
if (Global.gfx_postfx_motionblur_enabled)
if (Global.gfx_motion_vectors())
{
vp.main_texv = std::make_unique<opengl_texture>();
vp.main_texv->alloc_rendertarget(Global.gfx_postfx_motionblur_format, GL_RG, vp.width, vp.height);
@@ -544,13 +546,14 @@ bool opengl46_renderer::init_viewport(viewport_config &vp)
}
}
if (Global.gfx_postfx_motionblur_enabled)
if (Global.gfx_motion_vectors())
{
vp.msaa_rbv = std::make_unique<gl::renderbuffer>();
vp.msaa_rbv->alloc(Global.gfx_postfx_motionblur_format, vp.width, vp.height, samples);
vp.msaa_fb->attach(*vp.msaa_rbv, GL_COLOR_ATTACHMENT1);
WriteLog("motion blur enabled");
if (Global.gfx_postfx_motionblur_enabled)
WriteLog("motion blur enabled");
}
if( !vp.msaa_fb->is_complete() ) {
@@ -581,7 +584,7 @@ bool opengl46_renderer::init_viewport(viewport_config &vp)
vp.ssao_blur_fb->attach(*vp.ssao_blur_tex, GL_COLOR_ATTACHMENT0);
if (!vp.ssao_blur_fb->is_complete()) { ErrorLog("ssao blur framebuffer setup failed"); return false; }
if (Global.gfx_postfx_motionblur_enabled)
if (Global.gfx_motion_vectors())
{
vp.ao_apply_fb = std::make_unique<gl::framebuffer>();
vp.ao_apply_fb->attach(*vp.main_tex, GL_COLOR_ATTACHMENT0);
@@ -596,6 +599,24 @@ bool opengl46_renderer::init_viewport(viewport_config &vp)
vp.bloom = opengl46_exposure::make_bloom_chain(vp.width, vp.height);
if (!vp.bloom)
ErrorLog("bloom framebuffer setup failed, no bloom");
// the two images of the history of the temporal anti-aliasing, of the main view (not the eyes of the vr)
if (Global.gfx_taa() && vp.main && (vp.proj_type != viewport_config::vr_left) && (vp.proj_type != viewport_config::vr_right))
{
for (std::size_t i = 0; i < vp.taa_tex.size(); ++i)
{
vp.taa_tex[i] = std::make_unique<opengl_texture>();
vp.taa_tex[i]->alloc_rendertarget(GL_RGBA16F, GL_RGBA, vp.width, vp.height, 1, 1, GL_CLAMP_TO_EDGE);
vp.taa_fb[i] = std::make_unique<gl::framebuffer>();
vp.taa_fb[i]->attach(*vp.taa_tex[i], GL_COLOR_ATTACHMENT0);
if (!vp.taa_fb[i]->is_complete())
{
ErrorLog("temporal anti-aliasing framebuffer setup failed");
return false;
}
}
WriteLog("temporal anti-aliasing enabled");
}
}
if (vp.custom_backbuffer)
@@ -1541,6 +1562,18 @@ void opengl46_renderer::Render_pass(viewport_config &vp, rendermode const Mode)
break;
}
// the temporal anti-aliasing: the projection of the frame moved by a part of a pixel, the velocities pointed to the last frame
auto const taa{Taa_View(vp)};
m_motionhistory = taa;
m_cabfuture = glm::mat4{1.f};
if (taa)
{
auto const *cab{((false == FreeFlyModeFlag) && Global.render_cab && (simulation::Train != nullptr)) ? simulation::Train->Dynamic() : nullptr};
m_renderpass.pass_camera.projection() = m_taa.begin_frame(
m_renderpass.pass_camera.projection(), glm::dmat3{m_renderpass.pass_camera.modelview()}, m_renderpass.pass_camera.position(), cab, glm::ivec2{vp.width, vp.height});
m_cabfuture = m_taa.cab_future();
}
m_colorpass = m_renderpass; // cache pass data
// parts of the gpu time of the frame; the marks go through the main view only, in the context they were begun in
auto const gpupart = [this, &vp](char const *Name) {
@@ -1665,7 +1698,7 @@ void opengl46_renderer::Render_pass(viewport_config &vp, rendermode const Mode)
{
vp.msaa_fb->bind();
if (motionblur)
if (motionblur || taa)
vp.msaa_fb->setup_drawing(2);
else
vp.msaa_fb->setup_drawing(1);
@@ -1709,7 +1742,11 @@ void opengl46_renderer::Render_pass(viewport_config &vp, rendermode const Mode)
model_ubs.future = glm::mat4();
glm::mat4 future;
if (Global.pCamera.m_owner != nullptr)
if (taa)
{
future = m_taa.world_future();
}
else if (Global.pCamera.m_owner != nullptr)
{
auto const *vehicle = Global.pCamera.m_owner;
glm::mat4 mv = OpenGLMatrices.data(GL_MODELVIEW);
@@ -1792,7 +1829,7 @@ void opengl46_renderer::Render_pass(viewport_config &vp, rendermode const Mode)
if (!FreeFlyModeFlag && Global.Overcast <= 1.0f && Global.render_cab)
{
glDebug("render opaque cab");
model_ubs.future = glm::mat4();
model_ubs.future = m_cabfuture;
auto const *vehicle{ simulation::Train->Dynamic() };
// the lamp lighting the shadows of the sun, unless it is a light of its own
if( ( vehicle->InteriorLightLevel > 0.f ) && ( false == m_cablamplit ) ) {
@@ -1843,7 +1880,7 @@ void opengl46_renderer::Render_pass(viewport_config &vp, rendermode const Mode)
// cab render
if (false == FreeFlyModeFlag && Global.render_cab)
{
model_ubs.future = glm::mat4();
model_ubs.future = m_cabfuture;
auto *vehicle { simulation::Train->Dynamic() };
if( ( vehicle->InteriorLightLevel > 0.f ) && ( false == m_cablamplit ) ) {
setup_shadow_color( glm::min( colors::white, m_shadowcolor + glm::vec4( vehicle->InteriorLight * vehicle->InteriorLightLevel, 1.f ) ) );
@@ -1865,6 +1902,10 @@ void opengl46_renderer::Render_pass(viewport_config &vp, rendermode const Mode)
if( Global.Overcast > 1.0f ) {
// with the cab in place we can (finally) safely draw translucent part of the occupied vehicle
if( taa ) {
// the vehicle adds its own motion to the view of the world (the cab future has it already)
model_ubs.future = m_taa.world_future();
}
Render_Alpha( vehicle );
}
}
@@ -1893,7 +1934,7 @@ if (!Global.gfx_skippipeline)
gpupart("resolve, ambient occlusion, motion blur");
// the targets of the ambient occlusion are there only when it was on at the start
auto const aomode{(vp.ssao_tex != nullptr) ? Global.gfx_postfx_ao : 0};
if (motionblur)
if (motionblur || taa)
{
vp.main_fb->clear(GL_COLOR_BUFFER_BIT);
vp.main_fb->setup_drawing(2);
@@ -1905,9 +1946,26 @@ if (!Global.gfx_skippipeline)
if (aomode != 0)
Render_ambient_occlusion(vp, *vp.ao_apply_fb, aomode);
model_ubs.param[0].x = m_framerate / (1.0 / Global.gfx_postfx_motionblur_shutter);
model_ubo->update(model_ubs);
m_pfx_motionblur->apply({vp.main_tex.get(), vp.main_texv.get(), vp.main_texd.get()}, vp.main2_fb.get());
// the image the motion blur takes: the scene, or the scene blended into its history
auto *image{vp.main_tex.get()};
if (taa)
{
gpupart("temporal anti-aliasing");
Render_taa(vp);
image = vp.taa_tex[m_taa.target()].get();
}
if (motionblur)
{
if (taa)
gpupart("motion blur");
model_ubs.param[0].x = m_framerate / (1.0 / Global.gfx_postfx_motionblur_shutter);
model_ubo->update(model_ubs);
m_pfx_motionblur->apply({image, vp.main_texv.get(), vp.main_texd.get()}, vp.main2_fb.get());
}
else
{
vp.taa_fb[m_taa.target()]->blit_to(vp.main2_fb.get(), vp.width, vp.height, GL_COLOR_BUFFER_BIT, GL_COLOR_ATTACHMENT0);
}
}
else
{
@@ -1925,7 +1983,7 @@ if (!Global.gfx_skippipeline)
// (gfx.shadow.sdsm). the main framebuffer holds it resolved for the motion blur or the ambient occlusion
if (vp.main && Global.gfx_shadow_sdsm && (Shadow_Fit_Unavailable(vp) == nullptr))
{
auto const resolved{(vp.main_texd != nullptr) && (motionblur || (aomode != 0))};
auto const resolved{(vp.main_texd != nullptr) && (motionblur || taa || (aomode != 0))};
opengl46_shadow_fit::view const view{
m_colorpass.pass_camera.position(),
m_colorpass.pass_camera.projection(),
@@ -3961,7 +4019,7 @@ void opengl46_renderer::Render_Scene_Depth( glm::mat4 const &Future, bool const
}
if( ( false == FreeFlyModeFlag ) && ( Global.Overcast <= 1.0f ) && ( Global.render_cab ) ) {
model_ubs.future = glm::mat4();
model_ubs.future = m_cabfuture;
auto const *vehicle { simulation::Train->Dynamic() };
if( ( vehicle->InteriorLightLevel > 0.f ) && ( false == m_cablamplit ) ) {
setup_shadow_color( glm::min( colors::white, m_shadowcolor + glm::vec4( vehicle->InteriorLight * vehicle->InteriorLightLevel, 1.f ) ) );
@@ -3980,7 +4038,7 @@ void opengl46_renderer::Render_Scene_Depth( glm::mat4 const &Future, bool const
else {
// the conditions of the cab drawn early by the colour pass
if( ( false == FreeFlyModeFlag ) && ( Global.Overcast <= 1.0f ) && ( Global.render_cab ) ) {
model_ubs.future = glm::mat4();
model_ubs.future = m_cabfuture;
auto const *vehicle { simulation::Train->Dynamic() };
Render_cab( vehicle, vehicle->InteriorLightLevel, false );
}
@@ -6379,6 +6437,28 @@ void opengl46_renderer::Cull_Lights(viewport_config &vp)
m_cullstimer.end();
}
// the temporal anti-aliasing of the main view: the scene of the frame (main_tex, with its velocities and depth) blended into the
// history of the last frames (postfx_taa.frag), to the image of the history of this frame
void opengl46_renderer::Render_taa(viewport_config &vp)
{
auto const target{m_taa.target()};
// the near plane gives the distance of the pixels from their depth
model_ubs.param[0] = glm::vec4{m_taa.history_valid() ? 1.f : 0.f, m_colorpass.znear, 0.f, 0.f};
model_ubs.future = m_taa.reprojection();
model_ubo->update(model_ubs);
glViewport(0, 0, vp.width, vp.height);
// the alpha of the history is the lock of the thin things, written as it is (the blending is on after the translucent parts)
glDisable(GL_BLEND);
m_pfx_taa->apply({vp.main_tex.get(), vp.main_texv.get(), vp.main_texd.get(), vp.taa_tex[1 - target].get()}, vp.taa_fb[target].get());
if (m_blendingenabled)
glEnable(GL_BLEND);
}
bool opengl46_renderer::Taa_View(viewport_config const &vp) const
{
return Global.gfx_taa() && vp.main && (vp.taa_tex[0] != nullptr) && (m_pfx_taa != nullptr);
}
void opengl46_renderer::Render_ambient_occlusion(viewport_config &vp, gl::framebuffer &Target, int const Mode)
{
glDebug("ambient occlusion");

View File

@@ -46,6 +46,7 @@ http://mozilla.org/MPL/2.0/.
#include "rendering/opengl46/opengl46hiz.h"
#include "rendering/opengl46/opengl46shadowfit.h"
#include "rendering/common/scenewalk.h"
#include "rendering/common/temporalaa.h"
// bare-bones render controller, in lack of anything better yet
class opengl46_renderer : public gfx_renderer, public gfx::scene_walk {
@@ -267,6 +268,10 @@ class opengl46_renderer : public gfx_renderer, public gfx::scene_walk {
// main_tex alone, for the ambient occlusion to be applied to it before the motion blur (main_fb holds the depth it reads)
std::unique_ptr<gl::framebuffer> ao_apply_fb;
// the temporal anti-aliasing (main viewport only): the two images of the history, written in turn
std::array<std::unique_ptr<opengl_texture>, 2> taa_tex;
std::array<std::unique_ptr<gl::framebuffer>, 2> taa_fb;
// LDR backbuffer for offscreen rendering
std::unique_ptr<gl::framebuffer> backbuffer_fb;
std::unique_ptr<opengl_texture> backbuffer_tex;
@@ -451,6 +456,9 @@ class opengl46_renderer : public gfx_renderer, public gfx::scene_walk {
void Render_cluster_heatmap(viewport_config &vp, gl::framebuffer *Target, glm::ivec2 const &Targetsize);
// ambient occlusion from the resolved depth (vp.main_texd), multiplied into the image of Target. Mode: Global.gfx_postfx_ao
void Render_ambient_occlusion(viewport_config &vp, gl::framebuffer &Target, int const Mode);
// the temporal anti-aliasing of the main view (gfx.antialiasing taa), and whether the viewport has it
void Render_taa(viewport_config &vp);
bool Taa_View(viewport_config const &vp) const;
bool init_viewport(viewport_config &vp);
@@ -786,6 +794,8 @@ class opengl46_renderer : public gfx_renderer, public gfx::scene_walk {
std::vector<std::unique_ptr<viewport_config>> m_viewports;
std::unique_ptr<gl::postfx> m_pfx_motionblur;
std::unique_ptr<gl::postfx> m_pfx_taa;
gfx::temporal_aa m_taa;
std::unique_ptr<gl::postfx> m_pfx_tonemapping;
std::unique_ptr<gl::postfx> m_pfx_chromaticaberration;

View File

@@ -107,7 +107,8 @@ struct viewport_targets
{
VkExtent2D extent{0, 0};
VkSampleCountFlagBits samples{VK_SAMPLE_COUNT_1_BIT};
bool motion{false}; // the velocity is drawn, for the motion blur
bool motion{false}; // the velocity is drawn, for the motion blur or the temporal anti-aliasing
bool taa{false};
bool aberration{false};
// with more than one sample: the images drawn to, resolved to the ones below
std::shared_ptr<image> msaa_colour;
@@ -119,6 +120,9 @@ struct viewport_targets
std::shared_ptr<image> depth;
// the colour after the motion blur (hdr)
std::shared_ptr<image> blurred;
// the temporal anti-aliasing (gfx.antialiasing taa, the main viewport): the two images of the history, written in turn (hdr, the
// lock of the thin things in alpha)
std::array<std::shared_ptr<image>, 2> taa_history;
// the tonemapped image, before the chromatic aberration, and the one which goes to the window
std::shared_ptr<image> tonemapped;
std::shared_ptr<image> output;

View File

@@ -308,7 +308,7 @@ namespace
{
bool make_targets(vk14::allocator &Memory, vk14::deletion_queue &Deletions, std::uint64_t const Value, std::uint32_t &Formatscount, VkSampleCountFlags const Samplecounts, VkFormat const Bloomformat, vk14::viewport_targets &Targets,
int const Width, int const Height, bool const Srgb)
int const Width, int const Height, bool const Srgb, bool const Taa)
{
// as many samples as asked for, as far as the device has them for the colour and the depth
auto samples{static_cast<VkSampleCountFlagBits>(std::clamp(1 << Global.iMultisampling, 1, 8))};
@@ -316,11 +316,11 @@ bool make_targets(vk14::allocator &Memory, vk14::deletion_queue &Deletions, std:
{
samples = static_cast<VkSampleCountFlagBits>(samples >> 1);
}
auto const motion{Global.gfx_postfx_motionblur_enabled};
auto const motion{Global.gfx_motion_vectors()};
auto const aberration{Global.gfx_postfx_chromaticaberration_enabled};
VkExtent2D const extent{static_cast<std::uint32_t>(std::max(Width, 1)), static_cast<std::uint32_t>(std::max(Height, 1))};
if ((Targets.extent.width == extent.width) && (Targets.extent.height == extent.height) && (Targets.samples == samples) && (Targets.motion == motion) && (Targets.aberration == aberration) && (Targets.srgb == Srgb) &&
Targets.output)
if ((Targets.extent.width == extent.width) && (Targets.extent.height == extent.height) && (Targets.samples == samples) && (Targets.motion == motion) && (Targets.taa == Taa) && (Targets.aberration == aberration) &&
(Targets.srgb == Srgb) && Targets.output)
{
return true;
}
@@ -333,6 +333,7 @@ bool make_targets(vk14::allocator &Memory, vk14::deletion_queue &Deletions, std:
Targets.extent = extent;
Targets.samples = samples;
Targets.motion = motion;
Targets.taa = Taa;
Targets.aberration = aberration;
Targets.srgb = Srgb;
@@ -367,7 +368,15 @@ bool make_targets(vk14::allocator &Memory, vk14::deletion_queue &Deletions, std:
make(Targets.depth, depth, VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT | VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_TRANSFER_SRC_BIT, VK_SAMPLE_COUNT_1_BIT, "scene depth");
if (motion)
{
ok = ok && make(Targets.motion_image, velocity, sampledcolour, VK_SAMPLE_COUNT_1_BIT, "scene velocity") && make(Targets.blurred, hdr, sampledcolour, VK_SAMPLE_COUNT_1_BIT, "motion blurred");
ok = ok && make(Targets.motion_image, velocity, sampledcolour, VK_SAMPLE_COUNT_1_BIT, "scene velocity");
}
if (motion && Global.gfx_postfx_motionblur_enabled)
{
ok = ok && make(Targets.blurred, hdr, sampledcolour, VK_SAMPLE_COUNT_1_BIT, "motion blurred");
}
if (Taa)
{
ok = ok && make(Targets.taa_history[0], hdr, sampledcolour, VK_SAMPLE_COUNT_1_BIT, "taa history") && make(Targets.taa_history[1], hdr, sampledcolour, VK_SAMPLE_COUNT_1_BIT, "taa history");
}
auto const output{Srgb ? VK_FORMAT_R8G8B8A8_SRGB : VK_FORMAT_R8G8B8A8_UNORM};
if (aberration)
@@ -873,11 +882,17 @@ void vulkan14_renderer::render_scene()
{
auto const &viewport{m_viewports[i]};
auto const srgb{false == Global.gfx_shadergamma};
auto const *history{scene.targets[i].taa_history[0].get()};
if (false == make_targets(ctx.memory, ctx.deletions, ctx.uploads.current_value(), scene.formats_count, ctx.device.caps().limits.framebufferColorSampleCounts & ctx.device.caps().limits.framebufferDepthSampleCounts, scene.bloom_format,
scene.targets[i], viewport.width, viewport.height, srgb))
scene.targets[i], viewport.width, viewport.height, srgb, viewport.main && Global.gfx_taa()))
{
continue;
}
if (viewport.main && (scene.targets[i].taa_history[0].get() != history))
{
// new images of the history, nothing in them yet
m_taa.reset();
}
render_viewport(i);
}
m_current_viewport = (m_viewports.empty() ? nullptr : &m_viewports.front());
@@ -1206,6 +1221,17 @@ void vulkan14_renderer::render_viewport(std::size_t const Index)
m_current_viewport = &vp;
setup_pass(vp, m_renderpass, rendermode::color);
// the temporal anti-aliasing: the projection of the frame moved by a part of a pixel, the velocities pointed to the last frame
auto const taa{vp.main && targets.taa && Global.gfx_taa()};
m_motionhistory = taa;
m_cabfuture = glm::mat4{1.f};
if (taa)
{
auto const *cab{((false == FreeFlyModeFlag) && Global.render_cab && (simulation::Train != nullptr)) ? simulation::Train->Dynamic() : nullptr};
m_renderpass.pass_camera.projection() = m_taa.begin_frame(
m_renderpass.pass_camera.projection(), glm::dmat3{m_renderpass.pass_camera.modelview()}, m_renderpass.pass_camera.position(), cab, glm::ivec2{vp.width, vp.height});
m_cabfuture = m_taa.cab_future();
}
m_colorpass = m_renderpass;
scene_ubs.time = Timer::GetTime();
@@ -1294,13 +1320,18 @@ void vulkan14_renderer::render_viewport(std::size_t const Index)
}
// the motion blur only when something in the view moves (see opengl46_renderer::Render_pass())
auto const motionblur{targets.motion && (m_vehiclesmoved || ((Global.pCamera.m_owner != nullptr) && (Global.pCamera.m_owner->get_future_movement() != glm::dvec3{0.0})))};
auto const motionblur{Global.gfx_postfx_motionblur_enabled && targets.motion &&
(m_vehiclesmoved || ((Global.pCamera.m_owner != nullptr) && (Global.pCamera.m_owner->get_future_movement() != glm::dvec3{0.0})))};
setup_matrices();
m_renderpass.draw_stats = {};
model_ubs.future = glm::mat4();
glm::mat4 future;
if (Global.pCamera.m_owner != nullptr)
if (taa)
{
future = m_taa.world_future();
}
else if (Global.pCamera.m_owner != nullptr)
{
auto const *vehicle = Global.pCamera.m_owner;
glm::mat4 mv = OpenGLMatrices.data(GL_MODELVIEW);
@@ -1377,7 +1408,7 @@ void vulkan14_renderer::render_viewport(std::size_t const Index)
// without rain/snow we can render the cab early to limit the overdraw
if (!FreeFlyModeFlag && Global.Overcast <= 1.0f && Global.render_cab && simulation::Train)
{
model_ubs.future = glm::mat4();
model_ubs.future = m_cabfuture;
auto const *vehicle{simulation::Train->Dynamic()};
// the lamp lighting the shadows of the sun, unless it is a light of its own
if ((vehicle->InteriorLightLevel > 0.f) && (false == m_cablamplit))
@@ -1413,7 +1444,7 @@ void vulkan14_renderer::render_viewport(std::size_t const Index)
// cab render
if ((false == FreeFlyModeFlag) && Global.render_cab && simulation::Train)
{
model_ubs.future = glm::mat4();
model_ubs.future = m_cabfuture;
auto *vehicle{simulation::Train->Dynamic()};
if ((vehicle->InteriorLightLevel > 0.f) && (false == m_cablamplit))
{
@@ -1433,6 +1464,11 @@ void vulkan14_renderer::render_viewport(std::size_t const Index)
if (Global.Overcast > 1.0f)
{
// with the cab in place we can (finally) safely draw translucent part of the occupied vehicle
if (taa)
{
// the vehicle adds its own motion to the view of the world (the cab future has it already)
model_ubs.future = m_taa.world_future();
}
Render_Alpha(vehicle);
}
}
@@ -1529,7 +1565,7 @@ void vulkan14_renderer::render_scene_depth(std::size_t const Index, glm::mat4 co
// the conditions of the cab drawn early by the colour pass
if ((false == FreeFlyModeFlag) && (Global.Overcast <= 1.0f) && Global.render_cab && simulation::Train)
{
model_ubs.future = glm::mat4();
model_ubs.future = m_cabfuture;
auto const *vehicle{simulation::Train->Dynamic()};
Render_cab(vehicle, vehicle->InteriorLightLevel, false);
}
@@ -2669,11 +2705,26 @@ void vulkan14_renderer::render_post(std::size_t const Index, bool const Motionbl
render_ao(Index, aomode);
}
auto *hdr{targets.colour.get()};
// the temporal anti-aliasing: the scene blended into its history (the motion blur takes the result)
if (m_motionhistory && targets.taa && targets.taa_history[0] && targets.taa_history[1] && targets.motion_image)
{
auto const target{m_taa.target()};
vk14::draw_constants constants;
// the near plane gives the distance of the pixels from their depth
constants.data[0] = glm::vec4{m_taa.history_valid() ? 1.f : 0.f, m_colorpass.znear, 0.f, 0.f};
auto const &reprojection{m_taa.reprojection()};
for (int column = 0; column < 4; ++column)
{
constants.data[2 + column] = reprojection[column];
}
post_pass("postfx_taa.frag", *targets.taa_history[target], {targets.colour.get(), targets.motion_image.get(), targets.depth.get(), targets.taa_history[1 - target].get()}, constants, "temporal anti-aliasing");
hdr = targets.taa_history[target].get();
}
if (Motionblur && targets.blurred)
{
vk14::draw_constants constants;
constants.data[0].x = m_framerate / (1.0 / Global.gfx_postfx_motionblur_shutter);
post_pass("postfx_motionblur.frag", *targets.blurred, {targets.colour.get(), targets.motion_image.get(), targets.depth.get()}, constants, "resolve, ambient occlusion, motion blur");
post_pass("postfx_motionblur.frag", *targets.blurred, {hdr, targets.motion_image.get(), targets.depth.get()}, constants, "resolve, ambient occlusion, motion blur");
hdr = targets.blurred.get();
}
ctx.passes.begin_pass("exposure, bloom");

View File

@@ -28,6 +28,7 @@ http://mozilla.org/MPL/2.0/.
#include "rendering/common/weatherstate.h"
#include "rendering/common/shadowfit.h"
#include "rendering/common/gpuparticles.h"
#include "rendering/common/temporalaa.h"
#include "rendering/particles.h"
#include "model/material.h"
#include "vehicle/Camera.h"
@@ -399,6 +400,8 @@ class vulkan14_renderer : public gfx_renderer, public gfx::scene_walk
double m_environmentupdatetime{0.0};
glm::dvec3 m_environmentupdatelocation{0.0};
bool m_vehiclesmoved{true};
// the temporal anti-aliasing of the main viewport (gfx.antialiasing taa)
gfx::temporal_aa m_taa;
texture_handle m_suntexture{-1};
texture_handle m_moontexture{-1};
texture_handle m_smoketexture{-1};

View File

@@ -600,6 +600,10 @@ bool vulkan14_renderer::init_scene(std::string &Reason)
{
scene.post_fragments.emplace(name, ctx.shaders.get(name));
}
if (Global.gfx_taa())
{
scene.post_fragments.emplace("postfx_taa.frag", ctx.shaders.get("postfx_taa.frag"));
}
auto const build{ctx.shaders.get("cluster_build.comp")};
auto const cull{ctx.shaders.get("cluster_cull.comp")};
scene.cluster_build = ctx.pipelines.compute(build, scene.cluster_layout, {}, "cluster build");

View File

@@ -0,0 +1,25 @@
#version 460 core
#define STAGE_FRAGMENT 1
// the temporal anti-aliasing (taa_resolve.glsl). param[0].x: 1 with a history of the last frame, param[0].y: the near plane;
// future: the clip space of this frame to the one of the last frame, for the world standing still (gfx::temporal_aa::reprojection())
layout(location = 14) in vec2 f_coords;
layout(location = 0) out vec4 out_color;
#pragma texture(color_tex, 0, RGB)
layout(binding = 0) uniform sampler2D color_tex;
#pragma texture(velocity_tex, 1, RG)
layout(binding = 1) uniform sampler2D velocity_tex;
#pragma texture(depth_tex, 2, R)
layout(binding = 2) uniform sampler2D depth_tex;
#pragma texture(history_tex, 3, RGBA)
layout(binding = 3) uniform sampler2D history_tex;
#include "common.glsl"
#define TAA_HISTORY_VALID (param[0].x > 0.5)
#define TAA_REPROJECTION future
#define TAA_NEAR param[0].y
#include "taa_resolve.glsl"

View File

@@ -0,0 +1,184 @@
// the temporal anti-aliasing (gfx.antialiasing taa, gfx::temporal_aa): the frame, drawn with the projection moved by a part of a
// pixel, blended into the history of the last frames. the same in ogl46/ and vk14/; the stage file gives the images and
// TAA_HISTORY_VALID the history holds the last frame (false: the frame alone is taken)
// TAA_REPROJECTION the clip space of this frame (with the depth) to the one of the last frame, for the world standing still
// TAA_NEAR the near plane of the projection: a pixel is that much over its depth away (reverse z)
// the history: the colour (linear, hdr) and in alpha the lock of the thin things.
//
// the thin things (rails and wires far away, thinner than a pixel) are hit by some of the offsets only; a pixel of a frame which
// misses them has the colours of the ground or the sky all around, so the usual check of the history against the colours around
// the pixel would wipe them out of the history and they would flicker. a pixel where a frame shows a line across (brighter or
// darker than the neighbours on both sides, and not along) is locked: for a few frames its history is kept as it is, as long as the
// pixel belongs to the world standing still (where the place in the last frame is sure), moves slowly over the screen and is far
// enough not to be a part of the cab (the needles of the gauges, the screens) or of things passing close by
// the share of the frame in the result
const float TAA_CURRENT_SHARE = 0.1;
// the history kept within this many standard deviations of the colours around the pixel: wide for a pixel standing still on the
// screen, narrow for one moving across it this many pixels a frame
const float TAA_VARIANCE_WIDE = 1.5;
const float TAA_VARIANCE_NARROW = 1.0;
const float TAA_SPEED_NARROW = 8.0;
// the velocity drawn agrees with the world standing still within this many pixels (the velocity is a half float)
const float TAA_STILL_PIXELS = 0.25;
// the lock of a thin thing lasts as many frames as the offsets go round (gfx::temporal_aa), and holds while the pixel moves slower
// than this many pixels a frame
const float TAA_LOCK_DECAY = 1.0 / 8.0;
const float TAA_LOCK_SPEED_MIN = 2.0;
const float TAA_LOCK_SPEED_MAX = 6.0;
const float TAA_LOCK_DISTANCE_MIN = 20.0;
const float TAA_LOCK_DISTANCE_FULL = 40.0;
// a line across: brighter or darker than both neighbours by this share of the brightest of the three, and the neighbours along
// it closer to the pixel than half of that
const float TAA_THIN_CONTRAST = 0.1;
const float TAA_THIN_MINIMUM = 0.01;
// the largest value of a half float: the colour drawn kept within it, without the nan and the infinity which would stay in the
// history for good
const float TAA_HALF_MAX = 65504.0;
vec3 taa_clean(vec3 c)
{
return (any(isnan(c)) || any(isinf(c))) ? vec3(0.0) : clamp(c, vec3(0.0), vec3(TAA_HALF_MAX));
}
// the colour squeezed into 0..1 by its largest channel (a weight against the bright pixels), and back
vec3 taa_compress(vec3 c)
{
return c / (1.0 + max(c.r, max(c.g, c.b)));
}
vec3 taa_decompress(vec3 c)
{
return c / max(1.0 - max(c.r, max(c.g, c.b)), 1.0 / TAA_HALF_MAX);
}
vec3 taa_ycocg(vec3 c)
{
return vec3(0.25 * c.r + 0.5 * c.g + 0.25 * c.b, 0.5 * c.r - 0.5 * c.b, -0.25 * c.r + 0.5 * c.g - 0.25 * c.b);
}
vec3 taa_rgb(vec3 c)
{
return vec3(c.x + c.y - c.z, c.x + c.z, c.x - c.y - c.z);
}
// the history at the place, sharp: a catmull-rom spline through the texels, in five bilinear lookups (the corners left out)
vec3 taa_history_colour(vec2 uv, vec2 size)
{
vec2 position = uv * size;
vec2 texel1 = floor(position - 0.5) + 0.5;
vec2 f = position - texel1;
vec2 w0 = f * (-0.5 + f * (1.0 - 0.5 * f));
vec2 w1 = 1.0 + f * f * (-2.5 + 1.5 * f);
vec2 w2 = f * (0.5 + f * (2.0 - 1.5 * f));
vec2 w3 = f * f * (-0.5 + 0.5 * f);
vec2 w12 = w1 + w2;
vec2 uv0 = (texel1 - 1.0) / size;
vec2 uv3 = (texel1 + 2.0) / size;
vec2 uv12 = (texel1 + w2 / w12) / size;
vec3 result = textureLod(history_tex, vec2(uv12.x, uv0.y), 0.0).rgb * (w12.x * w0.y);
result += textureLod(history_tex, vec2(uv0.x, uv12.y), 0.0).rgb * (w0.x * w12.y);
result += textureLod(history_tex, uv12, 0.0).rgb * (w12.x * w12.y);
result += textureLod(history_tex, vec2(uv3.x, uv12.y), 0.0).rgb * (w3.x * w12.y);
result += textureLod(history_tex, vec2(uv12.x, uv3.y), 0.0).rgb * (w12.x * w3.y);
float weight = w12.x * w0.y + w0.x * w12.y + w12.x * w12.y + w3.x * w12.y + w12.x * w3.y;
return taa_clean(result / weight);
}
// the history brought towards the middle of the box of the colours around the pixel until it's inside it
vec3 taa_clip(vec3 history, vec3 centre, vec3 extent)
{
vec3 offset = history - centre;
vec3 units = abs(offset / max(extent, vec3(1.0 / 65504.0)));
float largest = max(units.x, max(units.y, units.z));
return (largest > 1.0) ? centre + offset / largest : history;
}
// the pixel against its neighbours on both sides (A, B) and along (C, D): a line across it
bool taa_line(float centre, float a, float b, float c, float d)
{
float da = a - centre;
float db = b - centre;
float across = min(abs(da), abs(db));
float contrast = max(TAA_THIN_MINIMUM, TAA_THIN_CONTRAST * max(centre, max(a, b)));
return (da * db > 0.0) && (across > contrast) && (max(abs(c - centre), abs(d - centre)) < 0.5 * across);
}
void main()
{
vec2 size = vec2(textureSize(color_tex, 0));
ivec2 pixel = ivec2(f_coords * size);
ivec2 last = ivec2(size) - 1;
// the colours around the pixel (squeezed, ycocg): their moments and brightness, and the nearest depth among them
vec3 m1 = vec3(0.0);
vec3 m2 = vec3(0.0);
float luma[9];
float nearest = -1.0;
ivec2 nearestpixel = pixel;
for (int y = -1; y <= 1; ++y)
{
for (int x = -1; x <= 1; ++x)
{
ivec2 p = clamp(pixel + ivec2(x, y), ivec2(0), last);
vec3 c = taa_ycocg(taa_compress(taa_clean(texelFetch(color_tex, p, 0).rgb)));
m1 += c;
m2 += c * c;
luma[(y + 1) * 3 + (x + 1)] = c.x;
// reverse z: the nearer, the greater
float d = texelFetch(depth_tex, p, 0).r;
if (d > nearest)
{
nearest = d;
nearestpixel = p;
}
}
}
vec3 frame = taa_clean(texelFetch(color_tex, pixel, 0).rgb);
if (false == TAA_HISTORY_VALID)
{
out_color = vec4(frame, 0.0);
return;
}
vec3 current = taa_ycocg(taa_compress(frame));
// the place of the pixel in the last frame: by the velocity drawn (of the nearest pixel around, so the edges go with what's in
// front), or for the world standing still by the depth. the sky has no velocity drawn and stands still
float depth = texelFetch(depth_tex, pixel, 0).r;
vec2 moved = f_coords + texelFetch(velocity_tex, nearestpixel, 0).xy;
vec4 world = TAA_REPROJECTION * vec4(f_coords * 2.0 - 1.0, depth, 1.0);
vec2 worlduv = (world.w > 0.0) ? world.xy / world.w * 0.5 + 0.5 : vec2(-1.0);
float difference = length((moved - worlduv) * size);
bool still = (world.w > 0.0) && ((depth <= 0.0) || (difference < max(TAA_STILL_PIXELS, 0.01 * length((moved - f_coords) * size))));
vec2 uv = still ? worlduv : moved;
if (any(lessThan(uv, vec2(0.0))) || any(greaterThan(uv, vec2(1.0))))
{
// out of the last frame: the frame alone
out_color = vec4(frame, 0.0);
return;
}
float speed = length((uv - f_coords) * size);
vec3 history = taa_ycocg(taa_compress(taa_history_colour(uv, size)));
float lock = textureLod(history_tex, uv, 0.0).a;
lock = isnan(lock) ? 0.0 : clamp(lock, 0.0, 1.0);
// a line across the pixel in this frame locks it; the lock wears off, and holds only where the place in the last frame is sure,
// the pixel moves slowly and is far away
bool line = taa_line(luma[4], luma[3], luma[5], luma[1], luma[7]) || taa_line(luma[4], luma[1], luma[7], luma[3], luma[5]) ||
taa_line(luma[4], luma[0], luma[8], luma[2], luma[6]) || taa_line(luma[4], luma[2], luma[6], luma[0], luma[8]);
lock = max(lock - TAA_LOCK_DECAY, line ? 1.0 : 0.0);
float distance = (depth > 0.0) ? TAA_NEAR / depth : TAA_LOCK_DISTANCE_FULL;
lock *= (still ? 1.0 : 0.0) * (1.0 - smoothstep(TAA_LOCK_SPEED_MIN, TAA_LOCK_SPEED_MAX, speed)) *
smoothstep(TAA_LOCK_DISTANCE_MIN, TAA_LOCK_DISTANCE_FULL, distance);
// the history within the colours around the pixel, but where it's locked
vec3 mean = m1 / 9.0;
vec3 deviation = sqrt(max(m2 / 9.0 - mean * mean, vec3(0.0)));
float gamma = mix(TAA_VARIANCE_WIDE, TAA_VARIANCE_NARROW, clamp(speed / TAA_SPEED_NARROW, 0.0, 1.0));
history = mix(taa_clip(history, mean, gamma * deviation), history, lock);
vec3 result = mix(history, current, TAA_CURRENT_SHARE);
out_color = vec4(taa_clean(taa_decompress(clamp(taa_rgb(result), vec3(0.0), vec3(1.0 - 1.0 / TAA_HALF_MAX)))), lock);
}

View File

@@ -0,0 +1,26 @@
#version 460 core
#define STAGE_FRAGMENT 1
#define LAYOUT_POST 1
#include "common.glsl"
// the temporal anti-aliasing (taa_resolve.glsl). param is pc.data of the push constants: param[0].x 1 with a history of the last
// frame, param[0].y the near plane; param[2..5] the columns of the matrix of the clip space of this frame to the one of the last
// frame, for the world standing still (gfx::temporal_aa::reprojection())
layout(location = 14) in vec2 f_coords;
layout(location = 0) out vec4 out_color;
#pragma texture(color_tex, 0, RGB)
layout(set = POST_SET, binding = POST_TEXTURE_FIRST + 0) uniform sampler2D color_tex;
#pragma texture(velocity_tex, 1, RG)
layout(set = POST_SET, binding = POST_TEXTURE_FIRST + 1) uniform sampler2D velocity_tex;
#pragma texture(depth_tex, 2, R)
layout(set = POST_SET, binding = POST_TEXTURE_FIRST + 2) uniform sampler2D depth_tex;
#pragma texture(history_tex, 3, RGBA)
layout(set = POST_SET, binding = POST_TEXTURE_FIRST + 3) uniform sampler2D history_tex;
#define TAA_HISTORY_VALID (param[0].x > 0.5)
#define TAA_REPROJECTION mat4(param[2], param[3], param[4], param[5])
#define TAA_NEAR param[0].y
#include "taa_resolve.glsl"

View File

@@ -0,0 +1,184 @@
// the temporal anti-aliasing (gfx.antialiasing taa, gfx::temporal_aa): the frame, drawn with the projection moved by a part of a
// pixel, blended into the history of the last frames. the same in ogl46/ and vk14/; the stage file gives the images and
// TAA_HISTORY_VALID the history holds the last frame (false: the frame alone is taken)
// TAA_REPROJECTION the clip space of this frame (with the depth) to the one of the last frame, for the world standing still
// TAA_NEAR the near plane of the projection: a pixel is that much over its depth away (reverse z)
// the history: the colour (linear, hdr) and in alpha the lock of the thin things.
//
// the thin things (rails and wires far away, thinner than a pixel) are hit by some of the offsets only; a pixel of a frame which
// misses them has the colours of the ground or the sky all around, so the usual check of the history against the colours around
// the pixel would wipe them out of the history and they would flicker. a pixel where a frame shows a line across (brighter or
// darker than the neighbours on both sides, and not along) is locked: for a few frames its history is kept as it is, as long as the
// pixel belongs to the world standing still (where the place in the last frame is sure), moves slowly over the screen and is far
// enough not to be a part of the cab (the needles of the gauges, the screens) or of things passing close by
// the share of the frame in the result
const float TAA_CURRENT_SHARE = 0.1;
// the history kept within this many standard deviations of the colours around the pixel: wide for a pixel standing still on the
// screen, narrow for one moving across it this many pixels a frame
const float TAA_VARIANCE_WIDE = 1.5;
const float TAA_VARIANCE_NARROW = 1.0;
const float TAA_SPEED_NARROW = 8.0;
// the velocity drawn agrees with the world standing still within this many pixels (the velocity is a half float)
const float TAA_STILL_PIXELS = 0.25;
// the lock of a thin thing lasts as many frames as the offsets go round (gfx::temporal_aa), and holds while the pixel moves slower
// than this many pixels a frame
const float TAA_LOCK_DECAY = 1.0 / 8.0;
const float TAA_LOCK_SPEED_MIN = 2.0;
const float TAA_LOCK_SPEED_MAX = 6.0;
const float TAA_LOCK_DISTANCE_MIN = 20.0;
const float TAA_LOCK_DISTANCE_FULL = 40.0;
// a line across: brighter or darker than both neighbours by this share of the brightest of the three, and the neighbours along
// it closer to the pixel than half of that
const float TAA_THIN_CONTRAST = 0.1;
const float TAA_THIN_MINIMUM = 0.01;
// the largest value of a half float: the colour drawn kept within it, without the nan and the infinity which would stay in the
// history for good
const float TAA_HALF_MAX = 65504.0;
vec3 taa_clean(vec3 c)
{
return (any(isnan(c)) || any(isinf(c))) ? vec3(0.0) : clamp(c, vec3(0.0), vec3(TAA_HALF_MAX));
}
// the colour squeezed into 0..1 by its largest channel (a weight against the bright pixels), and back
vec3 taa_compress(vec3 c)
{
return c / (1.0 + max(c.r, max(c.g, c.b)));
}
vec3 taa_decompress(vec3 c)
{
return c / max(1.0 - max(c.r, max(c.g, c.b)), 1.0 / TAA_HALF_MAX);
}
vec3 taa_ycocg(vec3 c)
{
return vec3(0.25 * c.r + 0.5 * c.g + 0.25 * c.b, 0.5 * c.r - 0.5 * c.b, -0.25 * c.r + 0.5 * c.g - 0.25 * c.b);
}
vec3 taa_rgb(vec3 c)
{
return vec3(c.x + c.y - c.z, c.x + c.z, c.x - c.y - c.z);
}
// the history at the place, sharp: a catmull-rom spline through the texels, in five bilinear lookups (the corners left out)
vec3 taa_history_colour(vec2 uv, vec2 size)
{
vec2 position = uv * size;
vec2 texel1 = floor(position - 0.5) + 0.5;
vec2 f = position - texel1;
vec2 w0 = f * (-0.5 + f * (1.0 - 0.5 * f));
vec2 w1 = 1.0 + f * f * (-2.5 + 1.5 * f);
vec2 w2 = f * (0.5 + f * (2.0 - 1.5 * f));
vec2 w3 = f * f * (-0.5 + 0.5 * f);
vec2 w12 = w1 + w2;
vec2 uv0 = (texel1 - 1.0) / size;
vec2 uv3 = (texel1 + 2.0) / size;
vec2 uv12 = (texel1 + w2 / w12) / size;
vec3 result = textureLod(history_tex, vec2(uv12.x, uv0.y), 0.0).rgb * (w12.x * w0.y);
result += textureLod(history_tex, vec2(uv0.x, uv12.y), 0.0).rgb * (w0.x * w12.y);
result += textureLod(history_tex, uv12, 0.0).rgb * (w12.x * w12.y);
result += textureLod(history_tex, vec2(uv3.x, uv12.y), 0.0).rgb * (w3.x * w12.y);
result += textureLod(history_tex, vec2(uv12.x, uv3.y), 0.0).rgb * (w12.x * w3.y);
float weight = w12.x * w0.y + w0.x * w12.y + w12.x * w12.y + w3.x * w12.y + w12.x * w3.y;
return taa_clean(result / weight);
}
// the history brought towards the middle of the box of the colours around the pixel until it's inside it
vec3 taa_clip(vec3 history, vec3 centre, vec3 extent)
{
vec3 offset = history - centre;
vec3 units = abs(offset / max(extent, vec3(1.0 / 65504.0)));
float largest = max(units.x, max(units.y, units.z));
return (largest > 1.0) ? centre + offset / largest : history;
}
// the pixel against its neighbours on both sides (A, B) and along (C, D): a line across it
bool taa_line(float centre, float a, float b, float c, float d)
{
float da = a - centre;
float db = b - centre;
float across = min(abs(da), abs(db));
float contrast = max(TAA_THIN_MINIMUM, TAA_THIN_CONTRAST * max(centre, max(a, b)));
return (da * db > 0.0) && (across > contrast) && (max(abs(c - centre), abs(d - centre)) < 0.5 * across);
}
void main()
{
vec2 size = vec2(textureSize(color_tex, 0));
ivec2 pixel = ivec2(f_coords * size);
ivec2 last = ivec2(size) - 1;
// the colours around the pixel (squeezed, ycocg): their moments and brightness, and the nearest depth among them
vec3 m1 = vec3(0.0);
vec3 m2 = vec3(0.0);
float luma[9];
float nearest = -1.0;
ivec2 nearestpixel = pixel;
for (int y = -1; y <= 1; ++y)
{
for (int x = -1; x <= 1; ++x)
{
ivec2 p = clamp(pixel + ivec2(x, y), ivec2(0), last);
vec3 c = taa_ycocg(taa_compress(taa_clean(texelFetch(color_tex, p, 0).rgb)));
m1 += c;
m2 += c * c;
luma[(y + 1) * 3 + (x + 1)] = c.x;
// reverse z: the nearer, the greater
float d = texelFetch(depth_tex, p, 0).r;
if (d > nearest)
{
nearest = d;
nearestpixel = p;
}
}
}
vec3 frame = taa_clean(texelFetch(color_tex, pixel, 0).rgb);
if (false == TAA_HISTORY_VALID)
{
out_color = vec4(frame, 0.0);
return;
}
vec3 current = taa_ycocg(taa_compress(frame));
// the place of the pixel in the last frame: by the velocity drawn (of the nearest pixel around, so the edges go with what's in
// front), or for the world standing still by the depth. the sky has no velocity drawn and stands still
float depth = texelFetch(depth_tex, pixel, 0).r;
vec2 moved = f_coords + texelFetch(velocity_tex, nearestpixel, 0).xy;
vec4 world = TAA_REPROJECTION * vec4(f_coords * 2.0 - 1.0, depth, 1.0);
vec2 worlduv = (world.w > 0.0) ? world.xy / world.w * 0.5 + 0.5 : vec2(-1.0);
float difference = length((moved - worlduv) * size);
bool still = (world.w > 0.0) && ((depth <= 0.0) || (difference < max(TAA_STILL_PIXELS, 0.01 * length((moved - f_coords) * size))));
vec2 uv = still ? worlduv : moved;
if (any(lessThan(uv, vec2(0.0))) || any(greaterThan(uv, vec2(1.0))))
{
// out of the last frame: the frame alone
out_color = vec4(frame, 0.0);
return;
}
float speed = length((uv - f_coords) * size);
vec3 history = taa_ycocg(taa_compress(taa_history_colour(uv, size)));
float lock = textureLod(history_tex, uv, 0.0).a;
lock = isnan(lock) ? 0.0 : clamp(lock, 0.0, 1.0);
// a line across the pixel in this frame locks it; the lock wears off, and holds only where the place in the last frame is sure,
// the pixel moves slowly and is far away
bool line = taa_line(luma[4], luma[3], luma[5], luma[1], luma[7]) || taa_line(luma[4], luma[1], luma[7], luma[3], luma[5]) ||
taa_line(luma[4], luma[0], luma[8], luma[2], luma[6]) || taa_line(luma[4], luma[2], luma[6], luma[0], luma[8]);
lock = max(lock - TAA_LOCK_DECAY, line ? 1.0 : 0.0);
float distance = (depth > 0.0) ? TAA_NEAR / depth : TAA_LOCK_DISTANCE_FULL;
lock *= (still ? 1.0 : 0.0) * (1.0 - smoothstep(TAA_LOCK_SPEED_MIN, TAA_LOCK_SPEED_MAX, speed)) *
smoothstep(TAA_LOCK_DISTANCE_MIN, TAA_LOCK_DISTANCE_FULL, distance);
// the history within the colours around the pixel, but where it's locked
vec3 mean = m1 / 9.0;
vec3 deviation = sqrt(max(m2 / 9.0 - mean * mean, vec3(0.0)));
float gamma = mix(TAA_VARIANCE_WIDE, TAA_VARIANCE_NARROW, clamp(speed / TAA_SPEED_NARROW, 0.0, 1.0));
history = mix(taa_clip(history, mean, gamma * deviation), history, lock);
vec3 result = mix(history, current, TAA_CURRENT_SHARE);
out_color = vec4(taa_clean(taa_decompress(clamp(taa_rgb(result), vec3(0.0), vec3(1.0 - 1.0 / TAA_HALF_MAX)))), lock);
}

View File

@@ -368,6 +368,7 @@ bool global_settings::ConfigParseGraphics(cParser& Parser, const std::string& to
{
ParseOne(Parser, iMultisampling, 1, false);
iMultisampling = std::clamp(iMultisampling, 0, 4);
gfx_antialiasing = (iMultisampling > 0 ? antialiasing_mode::msaa : antialiasing_mode::off);
return true;
}
@@ -1898,6 +1899,23 @@ global_settings::ConfigParse_gfx_sky( cParser &Parser, std::string_view const To
std::string level;
Parser >> level;
gfx_fog_quality = ( level == "low" || level == "1" ) ? 1 : ( level == "high" || level == "3" ) ? 3 : ( level == "ultra" || level == "4" ) ? 4 : 2; }
else if( Token == "gfx.antialiasing" ) {
// off, msaax2, msaax4, msaax8 or taa; it sets the multisampling too, the key which used to say it
Parser.getTokens( 1 );
std::string mode;
Parser >> mode;
std::transform( mode.begin(), mode.end(), mode.begin(), []( unsigned char c ) { return static_cast<char>( std::tolower( c ) ); } );
if( mode == "taa" ) {
gfx_antialiasing = antialiasing_mode::taa;
iMultisampling = 0; }
else if( mode == "msaax2" || mode == "msaax4" || mode == "msaax8" || mode == "msaax16" ) {
gfx_antialiasing = antialiasing_mode::msaa;
iMultisampling = ( mode == "msaax2" ) ? 1 : ( mode == "msaax4" ) ? 2 : ( mode == "msaax8" ) ? 3 : 4; }
else if( mode == "off" || mode == "no" || mode == "none" || mode == "0" ) {
gfx_antialiasing = antialiasing_mode::off;
iMultisampling = 0; }
else {
ErrorLog( "Bad config: unknown gfx.antialiasing \"" + mode + "\", expected off, msaax2, msaax4, msaax8 or taa" ); } }
else if( Token == "gfx.fog.range" ) { readvalue( gfx_fog_range, 100.f, 10000.f ); }
else if( Token == "gfx.fog.lights" ) { readvalue( gfx_fog_lights, 0.f, 10.f ); }
else if( Token == "gfx.fog.shadows" ) { readswitch( gfx_fog_shadows ); }
@@ -2033,6 +2051,7 @@ global_settings::export_as_text( std::ostream &Output ) const {
export_as_text( Output, "gfx.reflections.fidelity", reflectiontune.fidelity );
export_as_text( Output, "timespeed", fTimeSpeed );
export_as_text( Output, "multisampling", iMultisampling );
export_as_text( Output, "gfx.antialiasing", std::string{ gfx_antialiasing == antialiasing_mode::taa ? "taa" : ( gfx_antialiasing == antialiasing_mode::off || iMultisampling == 0 ) ? "off" : "msaax" + std::to_string( 1 << iMultisampling ) } );
export_as_text( Output, "latitude", fLatitudeDeg );
export_as_text( Output, "convertmodels", iConvertModels );
export_as_text( Output, "file.binary.terrain", file_binary_terrain );

View File

@@ -179,6 +179,12 @@ struct global_settings {
GLint iMaxTextureSize{ 4096 }; // maksymalny rozmiar tekstury
GLint iMaxCabTextureSize{ 4096 }; // largest allowed texture in vehicle cab
int iMultisampling{ 2 }; // tryb antyaliasingu: 0=brak,1=2px,2=4px,3=8px,4=16px
// gfx.antialiasing: off, msaax2/x4/x8 (the samples go to iMultisampling) or taa (one sample, with the history of the last frames;
// iMultisampling 0). the old key multisampling sets msaa or off. read at the start
enum class antialiasing_mode { off, msaa, taa };
antialiasing_mode gfx_antialiasing{ antialiasing_mode::msaa };
// the temporal anti-aliasing is on: asked for, and the post-processing which does it is there
bool gfx_taa() const { return ( gfx_antialiasing == antialiasing_mode::taa ) && ( false == gfx_skippipeline ); }
float SplineFidelity{ 1.f }; // determines segment size during conversion of splines to geometry
float SleeperDistance{ 250.f }; // max distance (in meters) at which per-track sleeper models are still drawn; 0 disables sleeper rendering entirely
std::string Sleepers3D; // model of the sleepers and switch timbers laid where the simulator makes the ballast; empty: none
@@ -358,6 +364,8 @@ struct global_settings {
// one for each of the last frames, instead of with glBufferSubData. read at the start
bool gfx_bufferstreaming = true;
bool gfx_postfx_motionblur_enabled = true;
// the colour pass writes the velocities of the pixels: for the motion blur, or for the temporal anti-aliasing
bool gfx_motion_vectors() const { return gfx_postfx_motionblur_enabled || gfx_taa(); }
float gfx_postfx_motionblur_shutter = 0.01f;
GLenum gfx_postfx_motionblur_format = GL_RG16F;
GLenum gfx_format_color = GL_RGB16F;