acdream/src/AcDream.App/Rendering/Shaders/mesh_atmospheric.frag

130 lines
5.6 KiB
GLSL
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#version 430 core
#extension GL_ARB_bindless_texture : require
in vec3 vNormal;
in vec2 vTexCoord;
in vec3 vWorldPos;
in vec3 vAmbientLocalLit;
in vec3 vDirectionalLit;
// Campaign V slice V6e: the table slot, not the bindless handle — see
// mesh_modern.vert. The lookup moved here because a Vulkan varying cannot
// carry a descriptor.
in flat uint vTextureIndex;
in flat uint vTextureLayer;
in flat float vOpacityMultiplier; // #188
in flat vec2 vSelectionLighting; // x=luminosity, y=diffuse
in flat uint vReceivesDirectionalShadow;
#include "directional_shadow_receiver.glsl"
// uRenderPass values (Phase N.5 Decision 2 — two-pass alpha-test):
// 0 = opaque pass — discard fragments with alpha < 0.95
// (lets the depth write succeed for solid pixels)
// 1 = translucent pass — covers AlphaBlend / Additive / InvAlpha;
// discard alpha >= 0.95 (already drawn opaque) and
// alpha < 0.05 (skip empty fragments — large
// transparent overdraw cost otherwise)
uniform int uRenderPass;
uniform int uLightDebug; // #176 stripe hunt (see mesh_modern.vert) — mode 3 handled here
// SceneLighting UBO — IDENTICAL layout to mesh_instanced.frag binding=1.
struct Light {
vec4 posAndKind;
vec4 dirAndRange;
vec4 colorAndIntensity;
vec4 coneAngleEtc;
};
layout(std140, ACDREAM_UBO_SET binding = 1) uniform SceneLighting {
Light uLights[8];
vec4 uCellAmbient;
vec4 uFogParams;
vec4 uFogColor;
vec4 uCameraAndTime;
};
// A7 (2026-06-15): per-vertex lighting moved to mesh_modern.vert (Gouraud) to match
// retail's fixed-function per-vertex T&L — a per-pixel evaluation made a hard "spotlight"
// pool. The SceneLighting UBO above is still declared here for fog (uFogParams/uFogColor/
// uCameraAndTime) + the lightning-flash bump; its uLights[]/uCellAmbient are now consumed
// in the vertex shader. The std140 layout must stay identical to the vert + the CPU upload.
vec3 applyFog(vec3 lit, vec3 worldPos) {
int mode = int(uFogParams.w);
if (mode == 0) return lit;
float d = length(worldPos - uCameraAndTime.xyz);
float fogStart = uFogParams.x;
float fogEnd = uFogParams.y;
float span = max(1e-3, fogEnd - fogStart);
float fog = clamp((d - fogStart) / span, 0.0, 1.0);
return mix(lit, uFogColor.xyz, fog);
}
out vec4 FragColor;
void main() {
vec4 color = ACDREAM_SAMPLE_ARRAY(vTextureIndex, vec3(vTexCoord, float(vTextureLayer)));
// Two-pass alpha-test (N.5 Decision 2).
// A.5 T20: opaque pass writes alpha as-sampled so GL_SAMPLE_ALPHA_TO_COVERAGE
// derives the MSAA sample mask from it — ClipMap foliage edges become smooth.
// Discard only fully-transparent (α < 0.05); the GPU handles coverage masking.
if (uRenderPass == 0) {
if (color.a < 0.05) discard; // opaque pass — kill truly empty only (A2C)
} else {
// Transparent pass.
//
// Phase Post-A.5 (ISSUE #52, 2026-05-10): do NOT discard α≥0.95 here.
// Native AC transparent-flagged surfaces routinely include
// effectively-opaque pixels — e.g. the Holtburg lifestone crystal core
// (surface 0x080011DE) which the spawn manifest classifies as
// transparent (batch.IsTransparent=True) but whose decoded texture
// alpha lands ≥0.95 across the visible surface. Those pixels still
// compose correctly under (SrcAlpha, 1-SrcAlpha) alpha-blending, so
// discarding them here threw away the whole crystal. The original
// N.5 §2 rationale (high-α fragments belong in the opaque pass) does
// not apply when the SURFACE is dat-flagged transparent — those
// pixels can't reach the opaque pass at all.
//
// Keep the α<0.05 short-circuit as a fragment-cost optimization
// (skip fully-empty pixels — saves blend bandwidth on alpha-keyed
// sprites with large transparent margins).
if (color.a < 0.05) discard;
}
// Only the authored outdoor directional term is shadowed. Ambient, local
// lights and material selection pulses remain exactly on the retail path.
vec3 surfaceToLight = normalize(uShadowLightDirectionAndSource.xyz);
float directionalVisibility = vReceivesDirectionalShadow != 0u
? acdreamDirectionalShadowVisibility(
vWorldPos,
normalize(vNormal),
uCameraAndTime.xyz,
surfaceToLight)
: 1.0;
vec3 sceneLit = vAmbientLocalLit
+ vDirectionalLit * directionalVisibility;
vec3 lit = vec3(vSelectionLighting.x)
+ vSelectionLighting.y * sceneLit;
// #176 stripe-hunt mode 3: show the raw per-vertex light field (texture
// ignored). Stripes visible HERE = a vertex-lighting artifact; absent =
// the pattern comes from texture/per-pixel machinery. Throwaway diagnostic.
if (uLightDebug == 3) {
FragColor = vec4(min(lit, vec3(1.0)), 1.0);
return;
}
// Lightning flash — additive scene bump (matches mesh_instanced.frag).
lit += uFogParams.z * vec3(0.6, 0.6, 0.75);
// Retail clamp per-channel to 1.0 (r13 §13.1).
lit = min(lit, vec3(1.0));
vec3 rgb = color.rgb * lit;
rgb = applyFog(rgb, vWorldPos);
// #188: multiply the FINAL alpha only — the discard thresholds above stay
// keyed on the raw sampled color.a, so the last few frames of a fade
// (multiplier crossing under 0.05) still ramp smoothly toward zero rather
// than popping invisible early against the discard cutoff.
FragColor = vec4(rgb, color.a * vOpacityMultiplier);
}