From aad2aa67da5b0c5b6a7eb9a87a5bace869d21636 Mon Sep 17 00:00:00 2001 From: Erik Date: Fri, 8 May 2026 20:05:35 +0200 Subject: [PATCH] =?UTF-8?q?phase(N.5)=20Task=205:=20mesh=5Fmodern.vert=20+?= =?UTF-8?q?=20.frag=20=E2=80=94=20bindless=20+=20SSBO=20+=20indirect?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit New entity shaders for the WB modern rendering path. Modeled on WB's StaticObjectModern.* but adapted to acdream's lighting model: - Drops uActiveCells (we cull cells on CPU in WbDrawDispatcher) - Drops uDrawIDOffset (full passes, no pagination) - Drops uHighlightColor (deferred to Phase B.4 follow-up; field reserved in InstanceData struct comment) - Preserves mesh_instanced's SceneLighting UBO at binding=1 with 8 lights, fog params, lightning flash, per-channel clamp — full visual identity vert reads InstanceData[] @ binding=0 indexed by gl_BaseInstanceARB + gl_InstanceID for the per-entity model matrix; reads BatchData[] @ binding=1 indexed by gl_DrawIDARB for the per-group bindless texture handle + layer. frag samples sampler2DArray reconstructed from a uvec2 bindless handle + uint layer. uRenderPass uniform picks two-pass alpha-test thresholds: 0 = opaque (discard alpha<0.95), 1 = transparent (discard alpha>=0.95 and alpha<0.05). Not yet wired to the dispatcher — Task 6 sets up shader load + capability detection in GameWindow; Task 7-10 rewrite the dispatcher to use SSBO + glMultiDrawElementsIndirect. Co-Authored-By: Claude Opus 4.7 (1M context) --- .../Rendering/Shaders/mesh_modern.frag | 96 +++++++++++++++++++ .../Rendering/Shaders/mesh_modern.vert | 53 ++++++++++ 2 files changed, 149 insertions(+) create mode 100644 src/AcDream.App/Rendering/Shaders/mesh_modern.frag create mode 100644 src/AcDream.App/Rendering/Shaders/mesh_modern.vert diff --git a/src/AcDream.App/Rendering/Shaders/mesh_modern.frag b/src/AcDream.App/Rendering/Shaders/mesh_modern.frag new file mode 100644 index 0000000..fef4491 --- /dev/null +++ b/src/AcDream.App/Rendering/Shaders/mesh_modern.frag @@ -0,0 +1,96 @@ +#version 430 core +#extension GL_ARB_bindless_texture : require + +in vec3 vNormal; +in vec2 vTexCoord; +in vec3 vWorldPos; +in flat uvec2 vTextureHandle; +in flat uint vTextureLayer; + +// 0 = opaque (discard alpha<0.95), 1 = transparent (discard alpha>=0.95) +uniform int uRenderPass; + +// SceneLighting UBO — IDENTICAL layout to mesh_instanced.frag binding=1. +struct Light { + vec4 posAndKind; + vec4 dirAndRange; + vec4 colorAndIntensity; + vec4 coneAngleEtc; +}; +layout(std140, binding = 1) uniform SceneLighting { + Light uLights[8]; + vec4 uCellAmbient; + vec4 uFogParams; + vec4 uFogColor; + vec4 uCameraAndTime; +}; + +vec3 accumulateLights(vec3 N, vec3 worldPos) { + vec3 lit = uCellAmbient.xyz; + int activeLights = int(uCellAmbient.w); + for (int i = 0; i < 8; ++i) { + if (i >= activeLights) break; + int kind = int(uLights[i].posAndKind.w); + vec3 Lcol = uLights[i].colorAndIntensity.xyz * uLights[i].colorAndIntensity.w; + if (kind == 0) { + vec3 Ldir = -uLights[i].dirAndRange.xyz; + float ndl = max(0.0, dot(N, Ldir)); + lit += Lcol * ndl; + } else { + vec3 toL = uLights[i].posAndKind.xyz - worldPos; + float d = length(toL); + float range = uLights[i].dirAndRange.w; + if (d < range && range > 1e-3) { + vec3 Ldir = toL / max(d, 1e-4); + float ndl = max(0.0, dot(N, Ldir)); + float atten = 1.0; + if (kind == 2) { + float cos_edge = cos(uLights[i].coneAngleEtc.x * 0.5); + float cos_l = dot(-Ldir, uLights[i].dirAndRange.xyz); + atten *= (cos_l > cos_edge) ? 1.0 : 0.0; + } + lit += Lcol * ndl * atten; + } + } + } + return lit; +} + +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() { + sampler2DArray tex = sampler2DArray(vTextureHandle); + vec4 color = texture(tex, vec3(vTexCoord, float(vTextureLayer))); + + // Two-pass alpha-test (N.5 Decision 2). + if (uRenderPass == 0) { + if (color.a < 0.95) discard; // opaque pass + } else { + if (color.a >= 0.95) discard; // transparent pass + if (color.a < 0.05) discard; // skip totally-empty + } + + vec3 N = normalize(vNormal); + vec3 lit = accumulateLights(N, vWorldPos); + + // 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); + FragColor = vec4(rgb, color.a); +} diff --git a/src/AcDream.App/Rendering/Shaders/mesh_modern.vert b/src/AcDream.App/Rendering/Shaders/mesh_modern.vert new file mode 100644 index 0000000..31dd6bb --- /dev/null +++ b/src/AcDream.App/Rendering/Shaders/mesh_modern.vert @@ -0,0 +1,53 @@ +#version 430 core +#extension GL_ARB_bindless_texture : require +#extension GL_ARB_shader_draw_parameters : require + +layout(location = 0) in vec3 aPosition; +layout(location = 1) in vec3 aNormal; +layout(location = 2) in vec2 aTexCoord; + +struct InstanceData { + mat4 transform; + // Reserved for Phase B.4 follow-up (selection-blink retail-faithful + // highlight): vec4 highlightColor; — extend stride here, increase the + // _instanceSsbo upload size in WbDrawDispatcher, add a flat varying out, + // and consume in mesh_modern.frag. +}; + +struct BatchData { + uvec2 textureHandle; // bindless handle for sampler2DArray + uint textureLayer; // layer index (always 0 for per-instance composites) + uint flags; // reserved +}; + +layout(std430, binding = 0) readonly buffer InstanceBuffer { + InstanceData Instances[]; +}; + +layout(std430, binding = 1) readonly buffer BatchBuffer { + BatchData Batches[]; +}; + +uniform mat4 uViewProjection; + +out vec3 vNormal; +out vec2 vTexCoord; +out vec3 vWorldPos; +out flat uvec2 vTextureHandle; +out flat uint vTextureLayer; + +void main() { + int instanceIndex = gl_BaseInstanceARB + gl_InstanceID; + mat4 model = Instances[instanceIndex].transform; + + vec4 worldPos = model * vec4(aPosition, 1.0); + gl_Position = uViewProjection * worldPos; + + vWorldPos = worldPos.xyz; + vNormal = normalize(mat3(model) * aNormal); + vTexCoord = aTexCoord; + + BatchData b = Batches[gl_DrawIDARB]; + vTextureHandle = b.textureHandle; + vTextureLayer = b.textureLayer; +}