Two runtime blockers discovered after merging the sky/weather/lighting branch: 1. GLSL reserved word: mesh.frag + mesh_instanced.frag used \`int active\` as a local. On GLSL ES / some drivers \`active\` is a reserved identifier and compile fails hard (\"ERROR: 0:38: 'active' : Reserved word\"). Renamed to \`activeLights\`. 2. SkyRenderer.EnsureMeshUploaded called DatCollection.Get<GfxObj> without the _datLock that wraps the streaming pipeline's dat reads. DatBinReader has shared buffer state; concurrent reads race and throw ArgumentOutOfRangeException from Vec2Duv.Unpack deep in the mesh parse. Wrapped both Get<GfxObj> and GfxObjMesh.Build in try/catch and cache a null entry on failure so we don't retry every frame and crash the render loop. Full fix would plumb _datLock into the sky renderer, left as a TODO. Client now stable end-to-end — in-world, spawn stream flowing, animation + audio + sky + light UBO all live. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
98 lines
3.2 KiB
GLSL
98 lines
3.2 KiB
GLSL
#version 430 core
|
|
|
|
in vec2 vTex;
|
|
in vec3 vWorldNormal;
|
|
in vec3 vWorldPos;
|
|
|
|
out vec4 fragColor;
|
|
|
|
// One 2D texture per draw call — same binding point as mesh.frag so the
|
|
// C# side can use the same TextureCache without a texture-array pipeline.
|
|
uniform sampler2D uDiffuse;
|
|
|
|
// Translucency kind — matches TranslucencyKind C# enum (same as mesh.frag):
|
|
// 0 = Opaque — depth write+test, no blend; shader never discards
|
|
// 1 = ClipMap — alpha-key discard at 0.5 (doors, windows, vegetation)
|
|
// 2 = AlphaBlend — GL blending handles compositing; do NOT discard
|
|
// 3 = Additive — GL additive blending; do NOT discard
|
|
// 4 = InvAlpha — GL inverted-alpha blending; do NOT discard
|
|
uniform int uTranslucencyKind;
|
|
|
|
// Phase G.1+G.2: shared scene-lighting UBO (see mesh.frag for layout docs).
|
|
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);
|
|
}
|
|
|
|
void main() {
|
|
vec4 color = texture(uDiffuse, vTex);
|
|
|
|
// Alpha cutout only for clip-map surfaces (doors, windows, vegetation).
|
|
if (uTranslucencyKind == 1 && color.a < 0.5) discard;
|
|
|
|
vec3 N = normalize(vWorldNormal);
|
|
vec3 lit = accumulateLights(N, vWorldPos);
|
|
|
|
// Lightning flash — additive scene bump.
|
|
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);
|
|
}
|