Replace the building and EnvCell detail replay with retail's exact single-pass stage result, including authored surface opacity, squared detail alpha, final-alpha clipping, and the original subset pipeline/order. Arm the ordered walk command in place to close #471, delete the replay pipelines/shaders, and advance prepared content to recipe 9. Mutation witnesses (each restored before commit): - X=a*qA: RetailDetailTextureContractTests.BothShaderFamiliesUseTheSharedOnePassSourceAndDebugPrecedesDetailSample line 174, missing materialAlpha * detail.a * detail.a. - X*=base alpha: same test line 175, forbidden baseTexel.a found. - CLIP against base alpha: EnvCellAlphaDrawSourceTests.ClipShaders_UseGreaterEqualForThePerRangeReference line 260, final-X conditional missing. - second detail draw: EnvCellAlphaDrawSourceTests.DetailOn_EveryEnvCellFamilyDrawsOnceInPlaceWithAuthoredOpacity line 105, Assert.Single saw 2 MDI calls. - straight-alpha substitution: WalkStaticStreamPopulatorTests.ImmediateBuildingDetail_RetainsOriginalFramebufferFamily line 1244, Additive first failed (only wb-mesh-alpha-1x recorded; InvAlpha also failed). - omit ordered arm: OrderPreservingSubmitterTests.PrepareThenDraw_OrdinaryBuildingClipBuildingOrdinary_ArmsOnePassInPlace line 305, expected (77,3.5), got (0,0). - omit atmospheric combine: RetailDetailTextureContractTests.BothShaderFamiliesUseTheSharedOnePassSourceAndDebugPrecedesDetailSample line 173, atmospheric shared include missing. - drop serialized opacity: ObjectMeshDataSerializerTests.SurfaceOpacity_RoundTripsBitExactlyAndDeterministically line 288, first reported 0.5 bits 1056964608 vs 1065353216. - stale detail arm: ordered adjacency test line 307, expected following ordinary (0,0), got (77,3.5). - per-frame surface map: EnvCellAlphaDrawSourceTests.ProductionWholeLeaf_WarmedScanSubmitRhiAndFilteredReplayDoNotAllocate line 178, expected 0 B, got 147456 B. Verification before commit: shader compiler 23/23; focused App 213/213; Content 75/75; Core Wb 10/10; launcher migration 6/6; Release solution build 0 warnings / 0 errors; git diff --check clean.
136 lines
4.9 KiB
GLSL
136 lines
4.9 KiB
GLSL
#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;
|
|
in flat float vSurfaceOpacity;
|
|
in flat uint vBatchFlags;
|
|
in flat uint vDetailCategory;
|
|
|
|
#include "directional_shadow_receiver.glsl"
|
|
#include "retail_detail_material.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
|
|
uniform uint uTextureIndexA;
|
|
uniform float uParamA;
|
|
uniform float uParamB;
|
|
|
|
// 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;
|
|
|
|
bool isRetailClipReference(float value) {
|
|
return abs(value - (100.0 / 255.0)) < 0.000001
|
|
|| abs(value - (200.0 / 255.0)) < 0.000001;
|
|
}
|
|
|
|
void main() {
|
|
vec4 color = ACDREAM_SAMPLE_ARRAY(vTextureIndex, vec3(vTexCoord, float(vTextureLayer)));
|
|
float alphaCutoff = isRetailClipReference(uParamB) ? uParamB : 0.05;
|
|
|
|
// 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) {
|
|
if (color.a < alphaCutoff) discard;
|
|
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));
|
|
|
|
bool detailActive = uParamA != 0.0
|
|
&& vDetailCategory != 0u
|
|
&& (vBatchFlags & 1u) != 0u;
|
|
vec3 rgb;
|
|
float alpha;
|
|
if (detailActive) {
|
|
vec4 detail = ACDREAM_SAMPLE_ARRAY(
|
|
uTextureIndexA,
|
|
vec3(vTexCoord * uParamA, 0.0));
|
|
RetailDetailMaterialResult material = acdreamRetailDetailMaterial(
|
|
color.rgb,
|
|
lit,
|
|
detail,
|
|
vSurfaceOpacity * vOpacityMultiplier);
|
|
rgb = material.rgb;
|
|
alpha = material.alpha;
|
|
} else {
|
|
rgb = color.rgb * lit;
|
|
alpha = color.a * vOpacityMultiplier;
|
|
}
|
|
|
|
if (detailActive ? alpha < alphaCutoff : color.a < alphaCutoff)
|
|
discard;
|
|
|
|
rgb = applyFog(rgb, vWorldPos);
|
|
FragColor = vec4(rgb, alpha);
|
|
}
|