18
0
mirror of https://github.com/MaSzyna-EU07/maszyna.git synced 2026-11-15 11:55:12 +01:00

fix(render): antialias alpha tested edges with multisampling (eu07/maszyna#449)

The sides of the trackbed (and grass, fences, foliage) are cut out of their
textures by the alpha test, which keeps or drops whole pixels, so their edges
stayed jagged at any multisampling level; only geometry edges got smoothed.

With multisampling on, the alpha tested materials of the opaque colour pass
now draw with GL_SAMPLE_ALPHA_TO_COVERAGE. Their shaders do the alpha test
with alpha_test_fail() of glsl_common and write as alpha the coverage of the
test edge: the alpha sharpened around the opacity threshold to go from 0 to 1
over about a pixel, so the edge keeps the place it had and gets the samples
it covers. The renderer turns alpha to coverage on per material, only for
shaders which use the helper and only in the opaque pass with a positive
opacity, so translucent, water, terrain and the other shaders are unchanged.
Without multisampling the shaders work as before.
This commit is contained in:
jerrrrycho
2026-11-08 01:34:35 +01:00
parent 68fdf1efdf
commit aecc73e893
23 changed files with 116 additions and 34 deletions

View File

@@ -8,6 +8,7 @@ struct _global
bool gfx_extraeffects;
bool gfx_shadergamma;
bool gfx_usegles;
int iMultisampling;
};
extern _global Global;

View File

@@ -11,6 +11,7 @@ void gl::glsl_common_setup()
"#define MOTIONBLUR_ENABLED " + std::to_string((int)Global.gfx_postfx_motionblur_enabled) + "\n" +
"#define POSTFX_ENABLED " + std::to_string((int)!Global.gfx_skippipeline) + "\n" +
"#define EXTRAEFFECTS_ENABLED " + std::to_string((int)Global.gfx_extraeffects) + "\n" +
"#define ALPHA_TO_COVERAGE_ENABLED " + std::to_string((int)(Global.iMultisampling > 0)) + "\n" +
"#define MAX_LIGHTS " + std::to_string(MAX_LIGHTS) + "U\n" +
"#define MAX_CASCADES " + std::to_string(MAX_CASCADES) + "U\n" +
"#define MAX_PARAMS " + std::to_string(MAX_PARAMS) + "U\n" +
@@ -110,5 +111,34 @@ void gl::glsl_common_setup()
}
#endif
#ifdef STAGE_FRAGMENT
// alpha tested surfaces (positive opacity, the opaque pass) get the coverage of their alpha
// test edge from GL_SAMPLE_ALPHA_TO_COVERAGE, which the renderer turns on for them with
// multisampling; otherwise the alpha test keeps or drops whole pixels and its edges stay
// jagged whatever the multisampling. the alpha is sharpened to go from 0 to 1 over about
// a pixel around the test threshold, so the edge keeps the place and sharpness the alpha
// test gave it. computed before any discard, which leaves the derivatives undefined
float alpha_coverage(float alpha)
{
#if ALPHA_TO_COVERAGE_ENABLED
if (opacity > 0.0)
return clamp((alpha - opacity) / max(fwidth(alpha), 0.0001) + 0.5, 0.0, 1.0);
#endif
return alpha;
}
// true for the fragments the alpha test drops: below the opacity threshold in the opaque
// pass, at or above it (negative opacity) in the translucent pass. with alpha to coverage
// only the fragments which cover no sample at all
bool alpha_test_fail(float alpha, float coverage)
{
#if ALPHA_TO_COVERAGE_ENABLED
if (opacity > 0.0)
return coverage <= 0.0;
#endif
return (opacity >= 0.0 ? (alpha < opacity) : (alpha >= -opacity));
}
#endif
)STRING";
}

View File

@@ -175,6 +175,8 @@ std::pair<GLuint, std::string> gl::shader::process_source(const std::string &fil
str += "vec4 FBOUT(vec4 x) { return " + (Global.gfx_shadergamma ? std::string("vec4(pow(x.rgb, vec3(1.0 / 2.2)), x.a)") : std::string("x")) + "; }\n";
str += read_file(basedir + filename);
// checked before the includes are expanded, since glsl_common defines the function
alpha_to_coverage = ( str.find( "alpha_test_fail(" ) != std::string::npos );
expand_includes(str, basedir);
parse_texture_entries(str);
@@ -381,6 +383,7 @@ void gl::program::attach(const gl::shader &s)
texture_conf.try_emplace(texturename, std::move(textureentry));
for (auto [paramname, paramentry] : s.param_conf)
param_conf.try_emplace(paramname, std::move(paramentry));
alpha_to_coverage = alpha_to_coverage || s.alpha_to_coverage;
glAttachShader(*this, *s);
}

View File

@@ -63,6 +63,9 @@ namespace gl
std::unordered_map<std::string, texture_entry> texture_conf;
std::unordered_map<std::string, param_entry> param_conf;
std::string name;
// the shader does its alpha test with alpha_test_fail() of glsl_common, so its alpha
// output can drive GL_SAMPLE_ALPHA_TO_COVERAGE in the opaque pass
bool alpha_to_coverage{ false };
#ifndef SHADERVALIDATOR_STANDALONE
private:
@@ -96,6 +99,7 @@ namespace gl
std::unordered_map<std::string, shader::texture_entry> texture_conf;
std::unordered_map<std::string, shader::param_entry> param_conf;
bool alpha_to_coverage{ false };
private:
void init();

View File

@@ -1820,6 +1820,18 @@ void opengl33_renderer::setup_matrices()
OpenGLMatrices.load_matrix(glm::mat4{glm::mat3{m_renderpass.pass_camera.modelview()}});
}
void opengl33_renderer::setup_alpha_to_coverage(bool const Enable)
{
if (Enable == m_alphatocoverageenabled)
return;
if (Enable)
glEnable(GL_SAMPLE_ALPHA_TO_COVERAGE);
else
glDisable(GL_SAMPLE_ALPHA_TO_COVERAGE);
m_alphatocoverageenabled = Enable;
}
void opengl33_renderer::setup_drawing(bool const Alpha)
{
if (Alpha)
@@ -1834,6 +1846,8 @@ void opengl33_renderer::setup_drawing(bool const Alpha)
glDepthMask(GL_TRUE);
m_blendingenabled = false;
}
// turned on again per material, by the ones which need it
setup_alpha_to_coverage(false);
switch (m_renderpass.draw_mode)
{
@@ -2374,6 +2388,16 @@ void opengl33_renderer::Bind_Material( material_handle const Material, TSubModel
else
model_ubs.alpha_mult = 1.0f;
// the alpha test drops or keeps whole pixels, so multisampling alone leaves its edges (e.g. sides
// of the trackbed textures) jagged. the alpha tested materials of the opaque colour pass get them
// antialiased with alpha to coverage instead; their shaders give the coverage as the alpha
setup_alpha_to_coverage(
Global.iMultisampling > 0
&& false == m_blendingenabled
&& m_renderpass.draw_mode == rendermode::color
&& model_ubs.opacity > 0.f
&& material.shader->alpha_to_coverage );
// materials which declare texture variants provide a different texture set per model instance,
// picked from the seed of the rendered instance and the variant group of the submodel
auto const &materialtextures{

View File

@@ -277,6 +277,8 @@ class opengl33_renderer : public gfx_renderer {
void setup_pass(viewport_config &Viewport, renderpass_config &Config, rendermode const Mode, float const Znear = 0.f, float const Zfar = 1.f, bool const Ignoredebug = false);
void setup_matrices();
void setup_drawing(bool const Alpha = false);
// alpha to coverage for the alpha tested materials of the opaque colour pass, when multisampled
void setup_alpha_to_coverage(bool const Enable);
void setup_shadow_unbind_map();
void setup_shadow_bind_map();
void setup_shadow_color( glm::vec4 const &Shadowcolor );
@@ -560,6 +562,7 @@ class opengl33_renderer : public gfx_renderer {
material_handle m_invalid_material;
bool m_blendingenabled;
bool m_alphatocoverageenabled{ false };
bool m_widelines_supported;

View File

@@ -29,7 +29,8 @@ void main()
{
vec4 tex_color = texture(diffuse, f_coord);
bool alphatestfail = ( opacity >= 0.0 ? (tex_color.a < opacity) : (tex_color.a >= -opacity) );
float alphacoverage = alpha_coverage(tex_color.a);
bool alphatestfail = alpha_test_fail(tex_color.a, alphacoverage);
if(alphatestfail)
discard;
// if (tex_color.a < opacity)
@@ -42,7 +43,7 @@ void main()
glossiness = abs(param[1].w);
fragcolor = apply_lights(fragcolor, fragnormal, tex_color.rgb, reflectivity, 0.0, shadow_tone);
vec4 color = vec4(apply_fog(fragcolor), tex_color.a * alpha_mult);
vec4 color = vec4(apply_fog(fragcolor), alphacoverage * alpha_mult);
/*
float distance = dot(f_pos.xyz, f_pos.xyz);
if( distance <= cascade_end.x ) { color.r += 0.25; }

View File

@@ -35,7 +35,8 @@ void main()
{
vec4 tex_color = texture(diffuse, f_coord);
bool alphatestfail = ( opacity >= 0.0 ? (tex_color.a < opacity) : (tex_color.a >= -opacity) );
float alphacoverage = alpha_coverage(tex_color.a);
bool alphatestfail = alpha_test_fail(tex_color.a, alphacoverage);
if(alphatestfail)
discard;
// if (tex_color.a < opacity)
@@ -52,7 +53,7 @@ void main()
glossiness = abs(param[1].w);
fragcolor = apply_lights(fragcolor, fragnormal, tex_color.rgb, reflectivity, specularity, shadow_tone);
vec4 color = vec4(apply_fog(fragcolor), tex_color.a * alpha_mult);
vec4 color = vec4(apply_fog(fragcolor), alphacoverage * alpha_mult);
#if POSTFX_ENABLED
out_color = color;
#else

View File

@@ -32,7 +32,8 @@ void main()
{
vec4 tex_color = texture(diffuse, f_coord);
bool alphatestfail = ( opacity >= 0.0 ? (tex_color.a < opacity) : (tex_color.a >= -opacity) );
float alphacoverage = alpha_coverage(tex_color.a);
bool alphatestfail = alpha_test_fail(tex_color.a, alphacoverage);
if(alphatestfail)
discard;
// if (tex_color.a < opacity)
@@ -46,7 +47,7 @@ void main()
metalic = texture(specgloss, f_coord).b;
fragcolor = apply_lights(fragcolor, fragnormal, tex_color.rgb, reflectivity, specularity, shadow_tone);
vec4 color = vec4(apply_fog(fragcolor), tex_color.a * alpha_mult);
vec4 color = vec4(apply_fog(fragcolor), alphacoverage * alpha_mult);
/*
float distance = dot(f_pos.xyz, f_pos.xyz);
if( distance <= cascade_end.x ) { color.r += 0.25; }

View File

@@ -39,7 +39,8 @@ void main()
{
vec4 tex_color = texture(diffuse, f_coord);
bool alphatestfail = ( opacity >= 0.0 ? (tex_color.a < opacity) : (tex_color.a >= -opacity) );
float alphacoverage = alpha_coverage(tex_color.a);
bool alphatestfail = alpha_test_fail(tex_color.a, alphacoverage);
if(alphatestfail)
discard;
@@ -64,7 +65,7 @@ void main()
float specularity = (tex_color.r + tex_color.g + tex_color.b) * 0.5;
fragcolor = apply_lights(fragcolor, fragnormal, tex_color.rgb, reflectivity, specularity, shadow_tone);
vec4 color = vec4(apply_fog(fragcolor), tex_color.a * alpha_mult);
vec4 color = vec4(apply_fog(fragcolor), alphacoverage * alpha_mult);
#if POSTFX_ENABLED
out_color = color;
#else

View File

@@ -42,7 +42,8 @@ void main()
{
vec4 tex_color = texture(diffuse, f_coord);
bool alphatestfail = ( opacity >= 0.0 ? (tex_color.a < opacity) : (tex_color.a >= -opacity) );
float alphacoverage = alpha_coverage(tex_color.a);
bool alphatestfail = alpha_test_fail(tex_color.a, alphacoverage);
if(alphatestfail)
discard;
// if (tex_color.a < opacity)
@@ -72,7 +73,7 @@ void main()
fragcolor = apply_lights(fragcolor, fragnormal, tex_color.rgb, reflectivity, specularity, shadow_tone);
vec4 color = vec4(apply_fog(fragcolor), tex_color.a * alpha_mult);
vec4 color = vec4(apply_fog(fragcolor), alphacoverage * alpha_mult);
#if POSTFX_ENABLED
out_color = color;
#else

View File

@@ -49,7 +49,8 @@ void main()
vec4 detailnormal_map = texture(detailnormalmap, f_coord_p * param[2].x);
vec4 tex_color = texture(diffuse, f_coord_p);
bool alphatestfail = ( opacity >= 0.0 ? (tex_color.a < opacity) : (tex_color.a >= -opacity) );
float alphacoverage = alpha_coverage(tex_color.a);
bool alphatestfail = alpha_test_fail(tex_color.a, alphacoverage);
if(alphatestfail)
discard;
// if (tex_color.a < opacity)
@@ -72,7 +73,7 @@ void main()
fragcolor = apply_lights(fragcolor, fragnormal, tex_color.rgb, reflectivity, specularity, shadow_tone);
vec4 color = vec4(apply_fog(fragcolor), tex_color.a * alpha_mult);
vec4 color = vec4(apply_fog(fragcolor), alphacoverage * alpha_mult);
#if POSTFX_ENABLED
out_color = color;
#else

View File

@@ -55,7 +55,8 @@ void main()
vec4 tex_color = texture(diffuse, f_coord_p);
vec4 specgloss_map = texture(specgloss, f_coord_p);
bool alphatestfail = ( opacity >= 0.0 ? (tex_color.a < opacity) : (tex_color.a >= -opacity) );
float alphacoverage = alpha_coverage(tex_color.a);
bool alphatestfail = alpha_test_fail(tex_color.a, alphacoverage);
if(alphatestfail)
discard;
@@ -78,7 +79,7 @@ void main()
fragcolor = apply_lights(fragcolor, fragnormal, tex_color.rgb, reflectivity, specularity, shadow_tone);
vec4 color = vec4(apply_fog(fragcolor), tex_color.a * alpha_mult);
vec4 color = vec4(apply_fog(fragcolor), alphacoverage * alpha_mult);
#if POSTFX_ENABLED
out_color = color;
#else

View File

@@ -34,7 +34,8 @@ void main()
{
vec4 tex_color = texture(diffuse, f_coord);
bool alphatestfail = ( opacity >= 0.0 ? (tex_color.a < opacity) : (tex_color.a >= -opacity) );
float alphacoverage = alpha_coverage(tex_color.a);
bool alphatestfail = alpha_test_fail(tex_color.a, alphacoverage);
if(alphatestfail)
discard;
// if (tex_color.a < opacity)
@@ -51,7 +52,7 @@ void main()
fragcolor = apply_lights(fragcolor, fragnormal, tex_color.rgb, reflectivity, specularity, shadow_tone);
vec4 color = vec4(apply_fog(fragcolor), tex_color.a * alpha_mult);
vec4 color = vec4(apply_fog(fragcolor), alphacoverage * alpha_mult);
#if POSTFX_ENABLED
out_color = color;
#else

View File

@@ -38,7 +38,8 @@ void main()
{
vec4 tex_color = texture(diffuse, f_coord);
bool alphatestfail = ( opacity >= 0.0 ? (tex_color.a < opacity) : (tex_color.a >= -opacity) );
float alphacoverage = alpha_coverage(tex_color.a);
bool alphatestfail = alpha_test_fail(tex_color.a, alphacoverage);
if(alphatestfail)
discard;
// if (tex_color.a < opacity)
@@ -57,7 +58,7 @@ void main()
fragcolor = apply_lights(fragcolor, fragnormal, tex_color.rgb, reflectivity, specularity, shadow_tone);
vec4 color = vec4(apply_fog(fragcolor), tex_color.a * alpha_mult);
vec4 color = vec4(apply_fog(fragcolor), alphacoverage * alpha_mult);
#if POSTFX_ENABLED
out_color = color;
#else

View File

@@ -42,7 +42,8 @@ void main()
vec4 tex_color = texture(diffuse, f_coord_p);
bool alphatestfail = ( opacity >= 0.0 ? (tex_color.a < opacity) : (tex_color.a >= -opacity) );
float alphacoverage = alpha_coverage(tex_color.a);
bool alphatestfail = alpha_test_fail(tex_color.a, alphacoverage);
if(alphatestfail)
discard;
// if (tex_color.a < opacity)
@@ -59,7 +60,7 @@ void main()
fragcolor = apply_lights(fragcolor, fragnormal, tex_color.rgb, reflectivity, specularity, shadow_tone);
vec4 color = vec4(apply_fog(fragcolor), tex_color.a * alpha_mult);
vec4 color = vec4(apply_fog(fragcolor), alphacoverage * alpha_mult);
#if POSTFX_ENABLED
out_color = color;
#else

View File

@@ -46,7 +46,8 @@ void main()
vec4 tex_color = texture(diffuse, f_coord_p);
bool alphatestfail = ( opacity >= 0.0 ? (tex_color.a < opacity) : (tex_color.a >= -opacity) );
float alphacoverage = alpha_coverage(tex_color.a);
bool alphatestfail = alpha_test_fail(tex_color.a, alphacoverage);
if(alphatestfail)
discard;
// if (tex_color.a < opacity)
@@ -65,7 +66,7 @@ void main()
fragcolor = apply_lights(fragcolor, fragnormal, tex_color.rgb, reflectivity, specularity, shadow_tone);
vec4 color = vec4(apply_fog(fragcolor), tex_color.a * alpha_mult);
vec4 color = vec4(apply_fog(fragcolor), alphacoverage * alpha_mult);
#if POSTFX_ENABLED
out_color = color;
#else

View File

@@ -32,7 +32,8 @@ void main()
{
vec4 tex_color = texture(diffuse, f_coord);
bool alphatestfail = ( opacity >= 0.0 ? (tex_color.a < opacity) : (tex_color.a >= -opacity) );
float alphacoverage = alpha_coverage(tex_color.a);
bool alphatestfail = alpha_test_fail(tex_color.a, alphacoverage);
if(alphatestfail)
discard;
// if (tex_color.a < opacity)
@@ -49,7 +50,7 @@ void main()
if(alphatestfail)
fragcolor.r = 1.0;
vec4 color = vec4(apply_fog(fragcolor), tex_color.a * alpha_mult);
vec4 color = vec4(apply_fog(fragcolor), alphacoverage * alpha_mult);
#if POSTFX_ENABLED
out_color = color;

View File

@@ -36,7 +36,8 @@ void main()
{
vec4 tex_color = texture(diffuse, f_coord);
bool alphatestfail = ( opacity >= 0.0 ? (tex_color.a < opacity) : (tex_color.a >= -opacity) );
float alphacoverage = alpha_coverage(tex_color.a);
bool alphatestfail = alpha_test_fail(tex_color.a, alphacoverage);
if(alphatestfail)
discard;
// if (tex_color.a < opacity)
@@ -54,7 +55,7 @@ void main()
if(alphatestfail)
fragcolor.r = 1.0;
vec4 color = vec4(apply_fog(fragcolor), tex_color.a * alpha_mult);
vec4 color = vec4(apply_fog(fragcolor), alphacoverage * alpha_mult);
#if POSTFX_ENABLED
out_color = color;

View File

@@ -34,7 +34,8 @@ void main()
{
vec4 tex_color = texture(diffuse, f_coord);
bool alphatestfail = ( opacity >= 0.0 ? (tex_color.a < opacity) : (tex_color.a >= -opacity) );
float alphacoverage = alpha_coverage(tex_color.a);
bool alphatestfail = alpha_test_fail(tex_color.a, alphacoverage);
if(alphatestfail)
discard;
// if (tex_color.a < opacity)
@@ -51,7 +52,7 @@ void main()
glossiness = abs(param[1].w);
fragcolor = apply_lights(fragcolor, fragnormal, tex_color.rgb, reflectivity, specularity, 1.0);
vec4 color = vec4(apply_fog(fragcolor), tex_color.a * alpha_mult);
vec4 color = vec4(apply_fog(fragcolor), alphacoverage * alpha_mult);
#if POSTFX_ENABLED
out_color = color;
#else

View File

@@ -38,7 +38,8 @@ void main()
{
vec4 tex_color = texture(diffuse, f_coord);
bool alphatestfail = ( opacity >= 0.0 ? (tex_color.a < opacity) : (tex_color.a >= -opacity) );
float alphacoverage = alpha_coverage(tex_color.a);
bool alphatestfail = alpha_test_fail(tex_color.a, alphacoverage);
if(alphatestfail)
discard;
// if (tex_color.a < opacity)
@@ -56,7 +57,7 @@ void main()
metalic = texture(specgloss, f_coord).b;
fragcolor = apply_lights(fragcolor, fragnormal, tex_color.rgb, reflectivity, specularity, 1.0);
vec4 color = vec4(apply_fog(fragcolor), tex_color.a * alpha_mult);
vec4 color = vec4(apply_fog(fragcolor), alphacoverage * alpha_mult);
#if POSTFX_ENABLED
out_color = color;
#else

View File

@@ -99,7 +99,8 @@ void main()
{
vec4 tex_color = texture(diffuse, f_coord);
bool alphatestfail = ( opacity >= 0.0 ? (tex_color.a < opacity) : (tex_color.a >= -opacity) );
float alphacoverage = alpha_coverage(tex_color.a);
bool alphatestfail = alpha_test_fail(tex_color.a, alphacoverage);
if(alphatestfail)
discard;
// if (tex_color.a < opacity)
@@ -115,7 +116,7 @@ void main()
float specularity = (tex_color.r + tex_color.g + tex_color.b) * 0.5;
fragcolor = apply_lights_sunless(fragcolor, fragnormal, tex_color.rgb, reflectivity, specularity, shadow_tone);
vec4 color = vec4(apply_fog(fragcolor), tex_color.a * alpha_mult);
vec4 color = vec4(apply_fog(fragcolor), alphacoverage * alpha_mult);
#if POSTFX_ENABLED
out_color = color;
#else

View File

@@ -113,7 +113,8 @@ void main()
{
vec4 tex_color = texture(diffuse, f_coord);
bool alphatestfail = ( opacity >= 0.0 ? (tex_color.a < opacity) : (tex_color.a >= -opacity) );
float alphacoverage = alpha_coverage(tex_color.a);
bool alphatestfail = alpha_test_fail(tex_color.a, alphacoverage);
if(alphatestfail)
discard;
// if (tex_color.a < opacity)
@@ -131,7 +132,7 @@ void main()
metalic = texture(specgloss, f_coord).b;
fragcolor = apply_lights_sunless(fragcolor, fragnormal, tex_color.rgb, reflectivity, specularity, shadow_tone);
vec4 color = vec4(apply_fog(fragcolor), tex_color.a * alpha_mult);
vec4 color = vec4(apply_fog(fragcolor), alphacoverage * alpha_mult);
#if POSTFX_ENABLED
out_color = color;
#else