#version 430 core #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; }; struct BatchData { uint textureIndex; uint _pad; uint textureLayer; uint flags; }; layout(std430, binding = 0) readonly buffer InstanceBuffer { InstanceData Instances[]; }; layout(std430, binding = 1) readonly buffer BatchBuffer { BatchData Batches[]; }; struct CellClip { uint count; uint _p0; uint _p1; uint _p2; vec4 planes[8]; }; layout(std430, binding = 2) readonly buffer ClipRegionBuf { CellClip clipRegions[]; }; layout(std430, binding = 3) readonly buffer ClipSlotBuf { uint instanceClipSlot[]; }; // Object renderer only: 1 for a retail building shell, 0 for ordinary // scenery/creatures/players. EnvCellRenderer sets uParamB=0 and ignores the // value, but still binds one valid word because Vulkan sees this static use. layout(std430, binding = 9) readonly buffer InstanceDetailCategoryBuf { uint instanceDetailCategory[]; }; // #188 per-instance opacity multiplier, identical binding and indexing to // mesh_modern.vert's InstanceAlphaBuf (binding 7). VM2's cdb read proved // retail's built-mesh detail combine is a single-pass texture-stage blend // whose stage-0 alpha is PREMODULATE(DIFFUSE, DIFFUSE) = diffuse.a * // detail.a (D3DPolyRender::SetSurface 0x0059c4d0) — the base subset's own // diffuse alpha gates how much detail shows through, exactly like the base // pass's translucency-fade multiplier already does for mesh_modern. 1.0 for // every opaque subset; <1.0 while a TransparentPartHook fade is in flight. layout(std430, binding = 7) readonly buffer InstanceAlphaBuf { float instanceAlpha[]; }; out gl_PerVertex { vec4 gl_Position; float gl_ClipDistance[8]; }; uniform mat4 uViewProjection; uniform int uDrawIDOffset; uniform uint uTextureIndexB; // absolute transform prefix in the shared pose arena uniform float uParamA; // detail UV tiling uniform float uParamB; // 1 = require building instance, 0 = EnvCell category out vec2 vBaseUv; out vec2 vDetailUv; out float vDetailOpacity; out vec3 vWorldPos; // review fix: mesh_detail.frag needs this for applyFog, // exactly like mesh_modern.vert's vWorldPos. out flat uint vBaseTextureIndex; out flat uint vBaseTextureLayer; out flat uint vBatchFlags; out flat uint vDetailCategory; void main() { int transformIndex = gl_BaseInstanceARB + gl_InstanceID; int instanceIndex = transformIndex - int(uTextureIndexB); vec4 worldPos = Instances[transformIndex].transform * vec4(aPosition, 1.0); gl_Position = uViewProjection * worldPos; vWorldPos = worldPos.xyz; uint slot = instanceClipSlot[instanceIndex]; CellClip clip = clipRegions[slot]; for (uint i = 0u; i < clip.count; ++i) gl_ClipDistance[i] = dot(clip.planes[i], gl_Position); for (uint i = clip.count; i < 8u; ++i) gl_ClipDistance[i] = 1.0; // No distance term: VM2 found the fade only exists in // D3DPolyRender::DrawPolyInternal (0x0059d7c0, the immediate-polygon // path) and only when the static noFadeDetail (0x00820e38, initialised // to 1) is 0. Every loaded CGfxObj sets use_built_mesh=1 // (CGfxObj::InitLoad 0x005346b0), so buildings/EnvCells never reach that // function; their attenuation is the LINEAR mip chain converging to the // texture mean, which the existing sampler already provides. vDetailOpacity = instanceAlpha[instanceIndex]; vBaseUv = aTexCoord; vDetailUv = aTexCoord * uParamA; BatchData batch = Batches[uDrawIDOffset + gl_DrawIDARB]; vBaseTextureIndex = batch.textureIndex; vBaseTextureLayer = batch.textureLayer; vBatchFlags = batch.flags; vDetailCategory = uParamB > 0.5 ? instanceDetailCategory[instanceIndex] : 1u; }