18
0
mirror of https://github.com/MaSzyna-EU07/maszyna.git synced 2026-11-14 16:50:53 +01:00
Files
maszyna/gl/glsl_common.cpp
jerrrrycho 04b55c866a fix(render): alpha to coverage only where it is on, full coverage on flat alpha
Review of the alpha to coverage change for alpha tested surfaces:

- An alpha which is flat over the pixel (fwidth 0) and equal to the opacity
  threshold got a coverage of 0.5, so e.g. opacity 1 on an opaque or DXT1
  texture turned half see-through with multisampling. Without an alpha edge
  in the pixel the plain test decides now: step(opacity, alpha).
- The shaders took the coverage path whenever they were built with
  multisampling, also where alpha to coverage is off (the reflections pass)
  or does nothing (the single-sampled model previews), and grew the cut-outs
  there by about half a pixel. model_ubo gets an alpha_to_coverage flag, set
  together with the GL state, and the shaders keep the plain alpha test
  without it. The previews no longer turn alpha to coverage on. The flag
  takes 4 of the 12 padding bytes after shadow_tone, so the std140 layout and
  the size of the block stay the same.
- The shader validator never set iMultisampling, so it never compiled the
  alpha to coverage branches. Its "features on" configuration uses the
  default of 2 now, on desktop GL and GLES.
2026-11-08 20:03:32 +01:00

153 lines
5.6 KiB
C++

#include "stdafx.h"
#include "glsl_common.h"
std::string gl::glsl_common;
void gl::glsl_common_setup()
{
glsl_common =
"#define SHADOWMAP_ENABLED " + std::to_string((int)Global.gfx_shadowmap_enabled) + "\n" +
"#define ENVMAP_ENABLED " + std::to_string((int)Global.gfx_envmap_enabled) + "\n" +
"#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" +
"#define MAX_INSTANCES_PER_BATCH " + std::to_string(MAX_INSTANCES_PER_BATCH) + "U\n" +
R"STRING(
const uint LIGHT_SPOT = 0U;
const uint LIGHT_POINT = 1U;
const uint LIGHT_DIR = 2U;
const uint LIGHT_HEADLIGHTS = 3U;
struct light_s
{
vec3 pos;
uint type;
vec3 dir;
float in_cutoff;
vec3 color;
float out_cutoff;
float linear;
float quadratic;
float intensity;
float ambient;
mat4 headlight_projection;
vec4 headlight_weights;
};
layout(std140) uniform light_ubo
{
vec3 ambient;
vec3 fog_color;
uint lights_count;
light_s lights[MAX_LIGHTS];
};
layout (std140) uniform model_ubo
{
mat4 modelview;
mat3 modelviewnormal;
vec4 param[MAX_PARAMS];
mat4 future;
float opacity;
float emission;
float fog_density;
float alpha_mult;
float shadow_tone;
float alpha_to_coverage;
};
layout (std140) uniform scene_ubo
{
mat4 projection;
mat4 inv_view;
mat4 lightview[MAX_CASCADES];
vec3 cascade_end;
float time;
vec4 rain_params;
vec4 wiper_pos;
vec4 wiper_timer_out;
vec4 wiper_timer_return;
};
// Per-instance modelview matrices for GPU-instanced draws.
// The UBO itself is declared in every stage so the binding stays valid,
// but the helper functions reference gl_InstanceID which is vertex-only,
// so they are guarded behind STAGE_VERTEX.
layout (std140) uniform instance_ubo
{
mat4 instance_modelview[MAX_INSTANCES_PER_BATCH];
};
#ifdef STAGE_VERTEX
// For non-instanced draws gl_InstanceID == 0 and slot 0 is permanently set
// to identity, so effective_modelview() reduces to model_ubo.modelview.
// For instanced draws the C++ side uploads camera-space root transforms for
// instances 0..N-1 and sets model_ubo.modelview to the submodel-local chain
// (computed from identity, NOT from the camera/instance), so the product
// gives the correct final transform per instance per submodel.
mat4 effective_modelview() {
return instance_modelview[gl_InstanceID] * modelview;
}
mat3 effective_modelviewnormal() {
// instance_modelview can include per-instance scale (uniform or per-axis),
// so its upper-3x3 is NOT its own inverse-transpose in the non-uniform
// case. Compute the correct normal matrix via transpose(inverse(...)).
// For pure rotation+translation this still produces the rotation matrix
// (one extra mat3 inverse per vertex — modern GPUs handle it cheaply).
mat3 instance_mv3 = mat3(instance_modelview[gl_InstanceID]);
mat3 instance_normal = transpose(inverse(instance_mv3));
return instance_normal * modelviewnormal;
}
#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.
// alpha_to_coverage of model_ubo tells whether alpha to coverage is on for this draw; where
// it isn't (reflections, previews) the shaders keep the plain alpha test
float alpha_coverage(float alpha)
{
#if ALPHA_TO_COVERAGE_ENABLED
if (opacity > 0.0 && alpha_to_coverage > 0.0)
{
// a flat alpha has no edge in the pixel and gets the plain test, so an alpha equal
// to the threshold stays fully covered
float fw = fwidth(alpha);
return (fw > 0.0 ? clamp((alpha - opacity) / fw + 0.5, 0.0, 1.0) : step(opacity, alpha));
}
#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 && alpha_to_coverage > 0.0)
return coverage <= 0.0;
#endif
return (opacity >= 0.0 ? (alpha < opacity) : (alpha >= -opacity));
}
#endif
)STRING";
}