layout(location = 0) out vec3 albedoData;
layout(location = 2) out vec4 colortex3Out;

/* DRAWBUFFERS:673 */

#include "/lib/Head/Common.inc"

#ifdef PARALLAX_SHADOW
	layout(location = 1) out vec4 colortex7Out;
#else
	layout(location = 1) out vec3 colortex7Out;
#endif



uniform sampler2D tex;
#ifdef MC_NORMAL_MAP
    uniform sampler2D normals;
#endif
#ifdef MC_SPECULAR_MAP
    uniform sampler2D specular;
#endif

uniform mat4 gbufferModelView;

uniform vec3 cameraPosition;
uniform vec3 worldLightVector;

in vec4 tint;
in vec2 texcoord;
in vec3 minecraftPos;
in vec3 viewPos;

in vec2 lightmap;

flat in mat3 tbnMatrix;

flat in uint materialIDs;

uniform int frameCounter;

#if defined PARALLAX || ANISOTROPIC_FILTER > 0
	in vec2 tileCoord;
	flat in vec2 tileOffset;
	flat in vec2 tileScale;

	vec2 OffsetCoord(in vec2 coord) { return tileOffset + tileScale * fract(coord); }
#endif

float InterleavedGradientNoiseTemporal(in vec2 coord) {
    return fract(52.9829189 * fract(0.06711056 * coord.x + 0.00583715 * coord.y + 0.00623715 * (frameCounter & 63)));
}

#ifdef PARALLAX
	#include "/lib/Surface/Parallax.glsl"
#endif

#if ANISOTROPIC_FILTER > 0
	vec3 AnisotropicFilter(in vec2 baseCoord, in mat2 texGrad) {
		mat2 J = inverse(texGrad);
		J = transpose(J) * J;

		float d = determinant(J);
		float t = J[0][0] + J[1][1];

		float D = sqrt(max0(t * t - 4.0 * d));
		float V = (t - D) * 0.5;
		float v = (t + D) * 0.5;
		float l = log2(inversesqrt(v));

		vec2 A = inversesqrt(V) * normalize(vec2(-J[0][1], J[0][0] - V));
		A /= tileScale;

		float c = 0.0;
		vec3 albedo = vec3(0.0);

		for (float i = 0.5 / ANISOTROPIC_FILTER - 0.5; i < 0.5; i += 1.0 / ANISOTROPIC_FILTER) {
			vec2 sampleCoord = OffsetCoord(baseCoord + i * A);

			vec4 albedoSample = textureLod(tex, sampleCoord, l);

			if (albedoSample.a > 1e-3) {
				albedo += albedoSample.rgb;
				++c;
			}
		}
		albedo /= max(c, 1.0);

		return albedo;
	}
#endif

#if defined IS_OVERWORLD
	uniform sampler2D noisetex;
	uniform sampler2D colortex7;

	uniform mat4 gbufferModelViewInverse;
	uniform float frameTimeCounter;
	uniform float wetnessCustom;

	#include "/lib/Surface/RainEffect.glsl"
#endif

// ============================================================
// SHADER LEAVES — РЕАЛИСТИЧНАЯ ЛИСТВА (USER SETTINGS)
// ============================================================

// Главный тумблер системы реалистичной листвы.
// При выключении листва рендерится стандартно (только SSS из SUBSERFACE_SCATTERING_MODE).
#define SHADER_LEAVES 1             // [0 1]

// --- СТИЛЬ НОРМАЛЕЙ ЛИСТЬЕВ ---
// 0 = Без генерации нормалей (flat)
// 1 = Процедурные нормали из альбедо (Sobel) — дают объём каждому листику
// 2 = Сфероидные нормали — листья скруглены как шар, хорошо для округлых крон
#define LEAVES_NORMAL_STYLE 1       // [0 1 2]

// Сила нормалей листьев (только для LEAVES_NORMAL_STYLE 1 и 2)
#define LEAVES_NORMAL_STRENGTH 1.8  // [0.5 1.0 1.5 1.8 2.5 3.5]

// --- SUBSURFACE SCATTERING ---
// Насыщенность просвечивания (зелёный оттенок сзади на свету)
#define LEAVES_SSS_AMOUNT 0.75      // [0.0 0.3 0.5 0.75 1.0]

// --- SURFACE WET SHEEN ---
// Тонкий глянцевый блик на листьях (кутикула)
// 0 = выкл, значения выше = сильнее
#define LEAVES_WETSHEEN 0.18        // [0.0 0.10 0.18 0.25 0.40]

// --- ЦВЕТОВАЯ ВАРИАЦИЯ ---
// Случайное смещение оттенка между листьями для разрушения однородности
#define LEAVES_COLOR_VARIATION 1    // [0 1]
#define LEAVES_COLOR_VAR_STRENGTH 0.12 // [0.05 0.12 0.20 0.30]

// --- AMBIENT OCCLUSION НА ЛИСТЬЯХ ---
// Затемняет листья внутри кроны по яркости (не настоящий AO, но выглядит как он)
#define LEAVES_INTERIOR_AO 1        // [0 1]
#define LEAVES_AO_STRENGTH 0.35     // [0.1 0.25 0.35 0.50]

// ============================================================

// === iPBR System ===
// Включает/выключает генерацию нормалей из альбедо (bump-эффект)
#define IPBR_ENABLED
// Включает отражения для металлических блоков НЕЗАВИСИМО от IPBR_ENABLED
// Если IPBR_ENABLED выключен, но IPBR_FORCE_REFLECTIONS включён —
// металлические блоки (iron, gold, diamond и т.д.) всё равно получат отражения
#define IPBR_FORCE_REFLECTIONS

// Стиль iPBR нормалей:
//   1 = Оригинальный (Sobel по яркости альбедо, gradX/gradY)
//   2 = Revelation-style (по luminance с учётом alpha, нормализация через AGN_RESOLUTION)
//   3 = Contrast-enhanced (усиление краёв по цветовым каналам отдельно, анизотропный отклик)
#define IPBR_STYLE 1 // [1 2 3]

#define IPBR_RESOLUTION 16 // [8 16 32 64 128 256 512 1024]

// --- Стиль 1: оригинальный Sobel по яркости ---
vec3 GenerateNormals_Style1(vec2 coord, vec2 texGradX, vec2 texGradY) {
    const float normalStrength = 2.5;
    vec2 baseStep = max(vec2(length(texGradX), length(texGradY)), vec2(1.0 / 4096.0));
    float resScale = float(IPBR_RESOLUTION) / 64.0;
    vec2 step = baseStep * resScale;

    float right = dot(textureGrad(tex, coord + vec2(step.x, 0.0),  texGradX, texGradY).rgb, vec3(0.299, 0.587, 0.114));
    float left  = dot(textureGrad(tex, coord - vec2(step.x, 0.0),  texGradX, texGradY).rgb, vec3(0.299, 0.587, 0.114));
    float up    = dot(textureGrad(tex, coord + vec2(0.0, step.y),   texGradX, texGradY).rgb, vec3(0.299, 0.587, 0.114));
    float down  = dot(textureGrad(tex, coord - vec2(0.0, step.y),   texGradX, texGradY).rgb, vec3(0.299, 0.587, 0.114));

    vec2 gradient = vec2(right - left, up - down) * normalStrength;
    return normalize(vec3(-gradient.x, -gradient.y, 1.0));
}

// --- Стиль 2: Revelation-style (luminance * alpha, AGN_RESOLUTION) ---
vec3 GenerateNormals_Style2(vec2 coord, vec2 texGradX, vec2 texGradY) {
    // Шаг пропорционален IPBR_RESOLUTION (аналог AGN_RESOLUTION в Revelation)
    vec2 bias = max(vec2(length(texGradX), length(texGradY)), vec2(1.0 / 4096.0));
    bias *= (16.0 / float(IPBR_RESOLUTION));

    vec4 sampleR = textureGrad(tex, coord + vec2(bias.x, 0.0),  texGradX, texGradY);
    vec4 sampleL = textureGrad(tex, coord - vec2(bias.x, 0.0),  texGradX, texGradY);
    vec4 sampleU = textureGrad(tex, coord + vec2(0.0, bias.y),   texGradX, texGradY);
    vec4 sampleD = textureGrad(tex, coord - vec2(0.0, bias.y),   texGradX, texGradY);

    // Height = luminance * alpha (как в Revelation AutoGenerateNormal)
    float heightR = dot(sampleR.rgb, vec3(0.2126, 0.7152, 0.0722)) * sampleR.a;
    float heightL = dot(sampleL.rgb, vec3(0.2126, 0.7152, 0.0722)) * sampleL.a;
    float heightU = dot(sampleU.rgb, vec3(0.2126, 0.7152, 0.0722)) * sampleU.a;
    float heightD = dot(sampleD.rgb, vec3(0.2126, 0.7152, 0.0722)) * sampleD.a;

    float deltaX = (heightL - heightR) * 2.5;
    float deltaY = (heightD - heightU) * 2.5;

    // Z нормализуется пропорционально разрешению (из Revelation: 32.0/AGN_RESOLUTION)
    float zStrength = 32.0 / float(IPBR_RESOLUTION);
    return normalize(vec3(deltaX, deltaY, zStrength));
}

// --- Стиль 3: Contrast-enhanced (анизотропный по RGB-каналам) ---
// Идея: каждый канал RGB вносит свой вклад в высоту с разным весом,
// что даёт более детальный отклик на цветные текстуры (медь, золото и т.д.)
vec3 GenerateNormals_Style3(vec2 coord, vec2 texGradX, vec2 texGradY) {
    vec2 baseStep = max(vec2(length(texGradX), length(texGradY)), vec2(1.0 / 4096.0));
    float resScale = float(IPBR_RESOLUTION) / 64.0;
    vec2 step = baseStep * resScale;

    vec3 sR = textureGrad(tex, coord + vec2(step.x, 0.0),  texGradX, texGradY).rgb;
    vec3 sL = textureGrad(tex, coord - vec2(step.x, 0.0),  texGradX, texGradY).rgb;
    vec3 sU = textureGrad(tex, coord + vec2(0.0, step.y),   texGradX, texGradY).rgb;
    vec3 sD = textureGrad(tex, coord - vec2(0.0, step.y),   texGradX, texGradY).rgb;

    // Анизотропные веса: красный — детали тёплых металлов, зелёный — окисление, синий — тени
    const vec3 weights = vec3(0.45, 0.38, 0.17);
    // Нелинейное усиление контраста: возводим в степень для резкости краёв
    float hR = pow(dot(sR, weights), 1.4);
    float hL = pow(dot(sL, weights), 1.4);
    float hU = pow(dot(sU, weights), 1.4);
    float hD = pow(dot(sD, weights), 1.4);

    float dX = (hL - hR) * 3.2;
    float dY = (hD - hU) * 3.2;
    return normalize(vec3(-dX, -dY, 1.0));
}


// ============================================================
// iPBR WATER — процедурная генерация ванильной текстуры воды

// Создаёт паттерн квадратных бликов как в ванилле Minecraft,
// запекает в albedo + specularData прямо здесь в Terrain.frag
// ============================================================


// === Диспетчер стилей ===
vec3 GenerateNormalsFromAlbedo(vec2 coord, vec2 texGradX, vec2 texGradY) {
    #if IPBR_STYLE == 2
        return GenerateNormals_Style2(coord, texGradX, texGradY);
    #elif IPBR_STYLE == 3
        return GenerateNormals_Style3(coord, texGradX, texGradY);
    #else
        return GenerateNormals_Style1(coord, texGradX, texGradY);
    #endif
}

// === DEBUG / OVERLAY MODE ===
#define LIGHT_OVERLAY 0 // [0 1]
#define CHUNK_OVERLAY 0 // [0 1]

// === Функция для определения деталей ===
float detectDetails(vec3 albedo) {
    float brightness = dot(albedo.rgb, vec3(0.299, 0.587, 0.114));
    if (brightness < 0.25) return 1.0;
    if (brightness < 0.35) return 0.7;
    float contrast = abs(brightness - 0.5);
    if (contrast > 0.25 && brightness < 0.4) return 0.5;
    return 0.0;
}

float detectCopperPurity(vec3 albedo) {
    float copperScore = albedo.r * (1.0 - albedo.b) * 0.7;
    float oxidizedScore = albedo.g * (1.0 - albedo.r) * 0.6;
    float total = copperScore + oxidizedScore + 0.001;
    return copperScore / total;
}

float detectPlankLines(vec3 albedo) {
    float brightness = dot(albedo.rgb, vec3(0.299, 0.587, 0.114));
    if (brightness < 0.30) return 1.0;
    if (brightness < 0.40) return 0.6;
    return 0.0;
}

bool isMetalPart(vec3 albedo, uint matID) {
    float brightness = dot(albedo.rgb, vec3(0.299, 0.587, 0.114));
    if (matID == 2020u) {
        bool isDarkMetal = brightness < 0.35;
        bool isGoldenMetal = (albedo.r > 0.6 && albedo.g > 0.5 && albedo.b < 0.4);
        return isDarkMetal || isGoldenMetal;
    }
    return false;
}


void applyOxidationLevel(inout float smoothness, inout float metalness, inout float fresnelStrength,
                         inout vec3 metalTint, inout float reflectionSharpness, int oxidationLevel) {

    metalness = 0.95;
    fresnelStrength = 0.92;

    if (oxidationLevel == 0) { // Clean copper
        smoothness = 0.88;
        metalTint = vec3(1.0, 0.75, 0.55);
        reflectionSharpness = 0.88;
    } else if (oxidationLevel == 1) { // Exposed
        smoothness = 0.80;
        metalTint = vec3(0.90, 0.68, 0.50);
        reflectionSharpness = 0.80;
    } else if (oxidationLevel == 2) { // Weathered
        smoothness = 0.72;
        metalTint = vec3(0.70, 0.78, 0.62);
        reflectionSharpness = 0.72;
    } else { // Oxidized
        smoothness = 0.65;
        metalTint = vec3(0.58, 0.80, 0.70);
        reflectionSharpness = 0.65;
    }
}

// ============================================================
// SHADER LEAVES — вспомогательные функции
// ============================================================
#ifdef SHADER_LEAVES

// Процедурные нормали для листьев — Sobel по яркости альбедо
// Более мягкий шаг чем у iPBR, чтобы не было артефактов на полупрозрачных краях
vec3 LeavesGenerateNormal_Sobel(vec2 coord, vec2 gX, vec2 gY) {
    float scale = 2.5 / float(LEAVES_NORMAL_STRENGTH); // обратная зависимость: большой strength → маленький шаг
    vec2 step = max(vec2(length(gX), length(gY)), vec2(1.0 / 2048.0)) * clamp(scale, 0.3, 4.0);

    float r = dot(textureGrad(tex, coord + vec2(step.x, 0.0),  gX, gY).rgb, vec3(0.299, 0.587, 0.114));
    float l = dot(textureGrad(tex, coord - vec2(step.x, 0.0),  gX, gY).rgb, vec3(0.299, 0.587, 0.114));
    float u = dot(textureGrad(tex, coord + vec2(0.0, step.y),  gX, gY).rgb, vec3(0.299, 0.587, 0.114));
    float d = dot(textureGrad(tex, coord - vec2(0.0, step.y),  gX, gY).rgb, vec3(0.299, 0.587, 0.114));

    vec2 grad = vec2(r - l, u - d) * float(LEAVES_NORMAL_STRENGTH);
    return normalize(vec3(-grad.x, -grad.y, 1.0));
}

// Сфероидные нормали — листва выглядит объёмной как шар/эллипсоид
// worldPos — позиция вершины, blockCenter — центр блока (floor + 0.5)
vec3 LeavesGenerateNormal_Sphere(vec3 worldPos, vec3 worldNorm) {
    // Отклонение от центра блока в плоскости ±0.5
    vec3 blockCenter = floor(worldPos) + vec3(0.5);
    vec3 offset = worldPos - blockCenter;
    // Сфероидное нормальное: горизонтально от центра + вертикаль из оригинала
    vec3 sphereN = normalize(vec3(offset.x * 2.0, worldNorm.y * 0.6, offset.z * 2.0));
    return normalize(mix(worldNorm, sphereN, float(LEAVES_NORMAL_STRENGTH) * 0.28));
}

// Цветовая вариация листьев — случайный оттенок на основе позиции блока
vec3 LeavesColorVariation(vec3 albedo, vec3 worldPos) {
    vec3 blockP = floor(worldPos + 0.5);
    float seed = dot(blockP, vec3(13.7, 27.3, 19.1));
    float rnd = fract(sin(seed) * 43758.5453);
    float rnd2 = fract(sin(seed * 1.7 + 11.3) * 12345.6789);

    // Смещение: чуть темнее/светлее и сдвиг тона зелёный↔жёлтый
    float brightVar = (rnd  - 0.5) * float(LEAVES_COLOR_VAR_STRENGTH) * 1.5;
    float hueShift  = (rnd2 - 0.5) * float(LEAVES_COLOR_VAR_STRENGTH);

    vec3 varied = albedo;
    varied.rgb += brightVar;
    varied.r   += hueShift * 0.6;
    varied.g   -= hueShift * 0.2;
    varied.b   -= hueShift * 0.5;
    return clamp(varied, 0.0, 1.0);
}

// Interior AO — затемнение листьев по skylight (имитирует глубину кроны)
float LeavesInteriorAO(vec2 lmap) {
    // skylight < 1.0 = внутри кроны → затемняем
    float skyFactor = lmap.y; // уже 0..1
    float ao = 1.0 - float(LEAVES_AO_STRENGTH) * (1.0 - skyFactor * skyFactor);
    return clamp(ao, 0.3, 1.0);
}

#endif // SHADER_LEAVES

void main() {
	vec3 normalData;
	vec4 albedo = tint;

	float dither = InterleavedGradientNoiseTemporal(gl_FragCoord.xy);

	vec2 texGradX = dFdx(texcoord);
	vec2 texGradY = dFdy(texcoord);
	mat2 texGrad = mat2(texGradX, texGradY);

	// =====================================================================
	// PARALLAX
	// =====================================================================
	#ifdef PARALLAX
		#define ReadTexture(tex) textureGrad(tex, parallaxCoord, texGradX, texGradY)

		vec2 parallaxCoord = texcoord;

		#ifdef SMOOTH_PARALLAX
			float sampleHeight = BilinearHeightSample(texcoord);
		#else
			float sampleHeight = textureGrad(normals, OffsetCoord(texcoord), texGradX, texGradY).a;
		#endif

		vec3 offsetCoord;
		if (sampleHeight < 0.999) {
			vec3 tangentViewVector = normalize(viewPos.xyz) * tbnMatrix;
			offsetCoord = CalculateParallax(tangentViewVector, texGrad, dither);
			parallaxCoord = OffsetCoord(offsetCoord.xy);
		}

		#ifdef MC_NORMAL_MAP
			normalData = ReadTexture(normals).rgb;
			DecodeNormalTex(normalData);
		#else
			normalData = vec3(0.0, 0.0, 1.0);
		#endif

		if (sampleHeight < 0.999) {
			#ifdef PARALLAX_SHADOW
				if (offsetCoord.z < 0.9999) {
					vec3 viewLightVector = mat3(gbufferModelView) * worldLightVector;
					if (dot(tbnMatrix[2], viewLightVector) > 1e-3) {
						colortex7Out.w = CalculateParallaxShadow(viewLightVector * tbnMatrix, offsetCoord, texGrad, dither);
					}
					#ifdef PARALLAX_BASED_NORMAL
						vec2 shift = 1e-2 * tileScale;
						float rD = textureGrad(normals, OffsetCoord(offsetCoord.xy + vec2(shift.x, 0.0)), texGradX, texGradY).a;
						float lD = textureGrad(normals, OffsetCoord(offsetCoord.xy - vec2(shift.x, 0.0)), texGradX, texGradY).a;
						float uD = textureGrad(normals, OffsetCoord(offsetCoord.xy + vec2(0.0, shift.y)), texGradX, texGradY).a;
						float dD = textureGrad(normals, OffsetCoord(offsetCoord.xy - vec2(0.0, shift.y)), texGradX, texGradY).a;
						normalData = vec3((lD - rD), (dD - uD), step(abs(lD - rD) + abs(dD - uD), 1e-3));
					#endif
				}
			#endif
		}

		#if ANISOTROPIC_FILTER > 0
			if (materialIDs != 15u) {
				albedo.rgb *= AnisotropicFilter((parallaxCoord - tileOffset) / tileScale, texGrad);
				albedo.a *= ReadTexture(tex).a;
			} else
		#endif
	#else
		#define ReadTexture(tex) textureGrad(tex, texcoord, texGradX, texGradY)

		#ifdef MC_NORMAL_MAP
			normalData = ReadTexture(normals).rgb;
			DecodeNormalTex(normalData);
		#else
			normalData = vec3(0.0, 0.0, 1.0);
		#endif

		#if ANISOTROPIC_FILTER > 0
			if (materialIDs != 15u) {
				albedo.rgb *= AnisotropicFilter(tileCoord, texGrad);
				albedo.a *= ReadTexture(tex).a;
			} else
		#endif
	#endif
	#if SHADER_GRASS == 1
	if (materialIDs != 65535u)
	#endif
	{ albedo *= ReadTexture(tex); }

	if (albedo.a < 0.1) { discard; return; }

	#ifdef WHITE_WORLD
		albedo.rgb = vec3(1.0);
	#endif



	// === iPBR System ===
	// Флаг для листвы/огня — не генерируем iPBR нормали для органики
	bool isFoliage = (materialIDs >= 1u && materialIDs <= 8u)
	              || materialIDs == 21u
	              || materialIDs == 22u
	              || materialIDs == 23u
	              || materialIDs == 24u
	              || materialIDs == 25u
	              || materialIDs == 26u
	              || materialIDs == 10070u
	              || materialIDs == 88u    // grass_block top (Shader Grass source)
	              || materialIDs == 65535u; // generated shader grass blades

	// === iPBR: генерация нормалей (только если IPBR_ENABLED) ===
	// Листья (7u) при активном SHADER_LEAVES генерируют свои нормали ниже — пропускаем здесь
	#ifdef IPBR_ENABLED
	bool iPBR_skipLeaves = false;
	#if defined(SHADER_LEAVES) && SHADER_LEAVES == 1 && LEAVES_NORMAL_STYLE > 0
		iPBR_skipLeaves = (materialIDs == 7u);
	#endif
	#if defined MC_NORMAL_MAP
		if (!isFoliage && !iPBR_skipLeaves) {
			vec3 generatedNormal = GenerateNormalsFromAlbedo(texcoord, texGradX, texGradY);
			normalData = normalize(normalData * 0.75 + generatedNormal * 0.25);
		}
	#else
		if (!isFoliage && !iPBR_skipLeaves) {
			normalData = GenerateNormalsFromAlbedo(texcoord, texGradX, texGradY);
		}
	#endif
	#endif // IPBR_ENABLED

	#ifdef MC_SPECULAR_MAP
		vec4 specularData = ReadTexture(specular);
	#else
		vec4 specularData = vec4(0.0);
	#endif

	// === iPBR WATER: процедурная ванильная текстура воды ===
	// Вода (17u) не проходит через gbuffers_terrain в стандартном пайплайне,
	// но если проходит — заменяем albedo и specular на ванильный паттерн


	// ============================================================
	// === SHADER LEAVES — реалистичная листва =====================
	// ============================================================
	#if defined(SHADER_LEAVES) && SHADER_LEAVES == 1
	bool isLeaves = (materialIDs == 7u);
	if (isLeaves) {

		// --- 1. Нормали листьев ---
		#if LEAVES_NORMAL_STYLE == 1
			// Sobel по альбедо — индивидуальный объём каждого листа
			vec3 leavesNorm = LeavesGenerateNormal_Sobel(texcoord, texGradX, texGradY);
			normalData = normalize(mix(normalData, leavesNorm, 0.82));
		#elif LEAVES_NORMAL_STYLE == 2
			// Сфероидные нормали — крона скруглена как шар
			vec3 worldN = mat3(gbufferModelViewInverse) * (tbnMatrix * normalData);
			vec3 sphereN = LeavesGenerateNormal_Sphere(minecraftPos, worldN);
			vec3 viewSphereN = mat3(gbufferModelView) * sphereN;
			// Конвертируем обратно в tangent space для консистентности
			normalData = normalize(transpose(tbnMatrix) * viewSphereN);
		#endif
		// LEAVES_NORMAL_STYLE == 0: нормали не меняем

		// --- 2. Цветовая вариация ---
		#if LEAVES_COLOR_VARIATION == 1
			albedo.rgb = LeavesColorVariation(albedo.rgb, minecraftPos);
		#endif

		// --- 3. Interior AO (затемнение внутри кроны) ---
		#if LEAVES_INTERIOR_AO == 1
			albedo.rgb *= LeavesInteriorAO(lightmap);
		#endif

		// --- 4. Поверхностный блеск (кутикула) ---
		#if LEAVES_WETSHEEN > 0.0
			// Добавляем небольшую гладкость — без металличности
			specularData.r = max(specularData.r, float(LEAVES_WETSHEEN));
			specularData.g = 0.0; // точно не металл
		#endif

		// --- 5. SSS (просвечивание листьев) ---
		// Перекрывает стандартный SSS из SUBSERFACE_SCATTERING_MODE
		float leafSSS = float(LEAVES_SSS_AMOUNT);
		#if TEXTURE_FORMAT == 0 && defined MC_SPECULAR_MAP
			specularData.b = max(specularData.b, leafSSS);
		#else
			specularData.a = max(specularData.a, leafSSS);
		#endif
	}
	#endif // SHADER_LEAVES
	// ============================================================

	// === MATERIAL SYSTEM ===
	bool isMetalBlock = false;
	float smoothness = specularData.r;
	float metalness = specularData.g;
	float fresnelStrength = 1.0;
	vec3 metalTint = vec3(1.0);
	float reflectionSharpness = 1.0;
	float detailMask = detectDetails(albedo.rgb);

	// === COPPER BLOCKS (10-57) ===
	if (materialIDs >= 10u && materialIDs <= 56u) {
		isMetalBlock = true;
		int oxidationLevel = int((materialIDs - 10u) / 12u);
		applyOxidationLevel(smoothness, metalness, fresnelStrength, metalTint, reflectionSharpness, oxidationLevel);
		smoothness = mix(smoothness, smoothness * 0.5, detailMask);
	}

	// === COPPER DOORS (58-65) ===
	else if (materialIDs >= 58u && materialIDs <= 65u) {
		isMetalBlock = true;
		int oxidationLevel = int((materialIDs - 58u) / 2u);
		applyOxidationLevel(smoothness, metalness, fresnelStrength, metalTint, reflectionSharpness, oxidationLevel);
		smoothness = mix(smoothness, smoothness * 0.5, detailMask);
	}

	// === COPPER TRAPDOORS (66-73) ===
	else if (materialIDs >= 66u && materialIDs <= 73u) {
		isMetalBlock = true;
		int oxidationLevel = int((materialIDs - 66u) / 2u);
		applyOxidationLevel(smoothness, metalness, fresnelStrength, metalTint, reflectionSharpness, oxidationLevel);
		smoothness = mix(smoothness, smoothness * 0.5, detailMask);
	}

	// === COPPER GRATES (74-79) ===
	else if (materialIDs >= 74u && materialIDs <= 79u) {
		isMetalBlock = true;
		int oxidationLevel = int((materialIDs - 74u) / 2u);
		applyOxidationLevel(smoothness, metalness, fresnelStrength, metalTint, reflectionSharpness, oxidationLevel);
		smoothness = mix(smoothness, smoothness * 0.5, detailMask);
	}

	// === IRON BLOCK (2010) ===
	else if (materialIDs == 2010u) {
		isMetalBlock = true;
		smoothness = 0.88;
		metalness = 0.95;
		fresnelStrength = 1.0;
		metalTint = vec3(0.95, 0.96, 1.0);
		reflectionSharpness = 0.90;
		smoothness = mix(smoothness, smoothness * 0.5, detailMask);
	}

	// === GOLD BLOCK (2011) ===
	else if (materialIDs == 2011u) {
		isMetalBlock = true;
		smoothness = 0.90;
		metalness = 0.95;
		fresnelStrength = 1.0;
		metalTint = vec3(1.0, 0.85, 0.50);
		reflectionSharpness = 0.92;
		smoothness = mix(smoothness, smoothness * 0.5, detailMask);
	}

	// === COPPER VARIANT (2012) ===
	else if (materialIDs == 2012u) {
		isMetalBlock = true;
		smoothness = 0.70;
		metalness = 0.88;
		fresnelStrength = 0.90;
		metalTint = vec3(0.95, 0.95, 1.0);
		reflectionSharpness = 0.75;
		smoothness = mix(smoothness, smoothness * 0.5, detailMask);
	}

	// === DIAMOND BLOCK (2017) ===
	else if (materialIDs == 2017u) {
		isMetalBlock = true;
		smoothness = 0.92;
		metalness = 0.85;
		fresnelStrength = 1.05;
		metalTint = vec3(0.60, 0.88, 1.0);
		reflectionSharpness = 0.95;
		smoothness = mix(smoothness, smoothness * 0.6, detailMask);
	}

	// === NETHERITE (2090) ===
	else if (materialIDs == 2090u) {
		isMetalBlock = true;
		smoothness = 0.68;
		metalness = 0.88;
		fresnelStrength = 0.80;
		metalTint = vec3(0.55, 0.52, 0.58);
		reflectionSharpness = 0.70;
		smoothness = mix(smoothness, smoothness * 0.5, detailMask);
	}

	// === SMOOTH STONE (2013) ===
	else if (materialIDs == 2013u) {
		isMetalBlock = false;
		smoothness = 0.45;
		metalness = 0.03;
	}

	// === CONCRETE (2014) ===
	else if (materialIDs == 2014u) {
		isMetalBlock = false;
		smoothness = 0.30;
		metalness = 0.01;
	}

	// === POLISHED STONE (2015) ===
	else if (materialIDs == 2015u) {
		isMetalBlock = false;
		smoothness = 0.55;
		metalness = 0.05;
	}

	// === OBSIDIAN (2016) ===
	else if (materialIDs == 2016u) {
		isMetalBlock = true;
		smoothness = 0.82;
		metalness = 0.30;
		fresnelStrength = 0.95;
		metalTint = vec3(0.85, 0.80, 0.95);
		reflectionSharpness = 0.88;
	}

	// === COPPER CHEST (2018) ===
	else if (materialIDs == 2018u) {
		if (isMetalPart(albedo.rgb, materialIDs)) {
			isMetalBlock = true;
			smoothness = 0.75;
			metalness = 0.85;
			fresnelStrength = 0.90;
			metalTint = vec3(1.0, 0.85, 0.75);
			reflectionSharpness = 0.80;
		}
	}

	// === BARREL (2019) ===
	else if (materialIDs == 2019u) {
		if (isMetalPart(albedo.rgb, materialIDs)) {
			isMetalBlock = true;
			smoothness = 0.65;
			metalness = 0.80;
			fresnelStrength = 0.85;
			metalTint = vec3(0.90, 0.90, 0.95);
			reflectionSharpness = 0.70;
		}
	}

	// === REGULAR CHEST (2020) ===
	else if (materialIDs == 2020u) {
		if (isMetalPart(albedo.rgb, materialIDs)) {
			isMetalBlock = true;
			smoothness = 0.70;
			metalness = 0.82;
			fresnelStrength = 0.88;
			bool isGolden = (albedo.r > 0.6 && albedo.g > 0.5 && albedo.b < 0.4);
			metalTint = isGolden ? vec3(1.0, 0.85, 0.50) : vec3(0.90, 0.90, 0.95);
			reflectionSharpness = 0.75;
		}
	}

	// === QUARTZ (2021) ===
	else if (materialIDs == 2021u) {
		isMetalBlock = true;
		smoothness = 0.97;
		metalness = 0.75;
		fresnelStrength = 1.30;
		metalTint = vec3(0.05, 0.05, 0.05);
		reflectionSharpness = 0.85;
		
		smoothness = mix(smoothness, smoothness * 0.5, detailMask);
	}

	// === IRON DOOR (2022) ===
	else if (materialIDs == 2022u) {
		isMetalBlock = true;
		smoothness = 0.68;
		metalness = 0.86;
		fresnelStrength = 0.88;
		metalTint = vec3(0.95, 0.95, 1.0);
		reflectionSharpness = 0.72;
		smoothness = mix(smoothness, smoothness * 0.45, detailMask);
	}

	// === IRON TRAPDOOR (2023) ===
	else if (materialIDs == 2023u) {
		isMetalBlock = true;
		smoothness = 0.66;
		metalness = 0.84;
		fresnelStrength = 0.86;
		metalTint = vec3(0.95, 0.95, 1.0);
		reflectionSharpness = 0.70;
		smoothness = mix(smoothness, smoothness * 0.45, detailMask);
	}

	// === OXIDIZED COPPER DOOR (2024) ===
	else if (materialIDs == 2024u) {
		isMetalBlock = true;
		applyOxidationLevel(smoothness, metalness, fresnelStrength, metalTint, reflectionSharpness, 3);
		smoothness = mix(smoothness, smoothness * 0.5, detailMask);
	}

	// === COPPER TRAPDOOR (2032) ===
	else if (materialIDs == 2032u) {
		isMetalBlock = true;
		applyOxidationLevel(smoothness, metalness, fresnelStrength, metalTint, reflectionSharpness, 0);
		smoothness = mix(smoothness, smoothness * 0.5, detailMask);
	}

	// === CHISELED COPPER (2048-2049) ===
	else if (materialIDs == 2048u || materialIDs == 2049u) {
		isMetalBlock = true;
		int oxidationLevel = (materialIDs == 2049u) ? 1 : 0;
		applyOxidationLevel(smoothness, metalness, fresnelStrength, metalTint, reflectionSharpness, oxidationLevel);
		smoothness = mix(smoothness, smoothness * 0.5, detailMask);
	}

	// === LIGHTNING ROD (2064) ===
	else if (materialIDs == 2064u) {
		isMetalBlock = true;
		smoothness = 0.78;
		metalness = 0.90;
		fresnelStrength = 0.92;
		metalTint = vec3(1.0, 0.85, 0.75);
		reflectionSharpness = 0.82;
		smoothness = mix(smoothness, smoothness * 0.5, detailMask);
	}

	// === WOOD PLANKS (2068-2076) — без отражений ===
	else if (materialIDs >= 2068u && materialIDs <= 2076u) {
		isMetalBlock = false;
		smoothness = 0.15;
		metalness = 0.0;
		fresnelStrength = 0.0;
		reflectionSharpness = 0.0;
	}

	// === COPPER BULBS ON (80-87) ===
	else if (materialIDs >= 80u && materialIDs <= 87u) {
		isMetalBlock = true;
		int oxidationLevel = int((materialIDs - 80u) / 2u);
		applyOxidationLevel(smoothness, metalness, fresnelStrength, metalTint, reflectionSharpness, oxidationLevel);
		smoothness = mix(smoothness, smoothness * 0.5, detailMask);
	}

	// === COPPER BULBS OFF (90-95) === // block.10088-89 unused / assigned to grass_block
	else if (materialIDs >= 90u && materialIDs <= 95u) {
		isMetalBlock = true;
		int oxidationLevel = int((materialIDs - 88u) / 2u);
		applyOxidationLevel(smoothness, metalness, fresnelStrength, metalTint, reflectionSharpness, oxidationLevel);
		smoothness = mix(smoothness, smoothness * 0.5, detailMask);
	}

	// === CHORUS PLANT (2088) ===
	else if (materialIDs == 2088u) {
		isMetalBlock = false;
		smoothness = 0.20;
		metalness = 0.0;
	}

	// === COPPER LANTERNS (3060-3067) — без отражений ===
	else if (materialIDs >= 3060u && materialIDs <= 3067u) {
		isMetalBlock = false;
		smoothness = 0.0;
		metalness = 0.0;
		fresnelStrength = 0.0;
		reflectionSharpness = 0.0;
	}

	// === COLORED CONCRETE (3070-3075) ===
	else if (materialIDs >= 3070u && materialIDs <= 3075u) {
		isMetalBlock = false;
		smoothness = 0.32;
		metalness = 0.01;
	}

    // === Старые materialIDs ===
    if (materialIDs == 9u || materialIDs == 58u) {
        if (materialIDs == 9u && albedo.r > 0.6 && albedo.g > 0.6 && albedo.b > 0.6) {
            specularData.r = 0.9; specularData.g = 0.2; specularData.b = 0.0;
        }  {
            specularData.r = 0.7; specularData.g = 0.3; specularData.b = 0.0;
        }  {
            specularData.r = 0.95; specularData.g = 0.1; specularData.b = 0.0;
            albedo.rgb *= vec3(0.8, 0.9, 1.0);
        }
    }

	// === МЕЧ (15) ===
	if (materialIDs == 15u) {
		specularData.r = 0.95;
		specularData.g = 1.0;
		specularData.b = 0.0;
		specularData.a = 0.0;
		albedo.rgb = mix(albedo.rgb, albedo.rgb * vec3(0.9, 0.95, 1.0), 0.3);
	}

	// === Факелы, фонари, огонь — полный сброс PBR ===
	if (materialIDs >= 21u && materialIDs <= 26u) {
		isMetalBlock = false;
		specularData = vec4(0.0);
		smoothness = 0.0;
		metalness = 0.0;
		fresnelStrength = 0.0;
		reflectionSharpness = 0.0;
	}

	// === iPBR fallback: нет PBR-текстур ===
	#ifndef MC_SPECULAR_MAP
	if (!isMetalBlock && !isFoliage && smoothness < 0.01) {
		float brightness = dot(albedo.rgb, vec3(0.299, 0.587, 0.114));
		float colorVariance = length(albedo.rgb - vec3(brightness));
		float darkBoost = (1.0 - brightness) * 0.25;
		smoothness = 0.06 + darkBoost + colorVariance * 0.08;
		metalness = 0.0;
		if (brightness < 0.18) {
			smoothness += 0.12;
			metalness = 0.04;
		}
		specularData.r = smoothness;
		specularData.g = metalness;
	}
	#endif

	// === Вертикальный градиент для травы ===
	bool isGrass = (materialIDs >= 1u && materialIDs <= 6u);
	if (isGrass) {
		bool isGreenish = albedo.g > albedo.r * 0.85 && albedo.g > albedo.b * 1.1 && albedo.g > 0.15;
		if (isGreenish) {
			float localY = fract(minecraftPos.y);
			float gradient = localY * 0.30;
			albedo.r = min(1.0, albedo.r + gradient * 0.45);
			albedo.g = min(1.0, albedo.g + gradient * 0.08);
			albedo.b = max(0.0, albedo.b - gradient * 0.04);
		}
	}

	// === IPBR_FORCE_REFLECTIONS: если IPBR выключен — сбрасываем отражения,
	// КРОМЕ случая когда явно включён IPBR_FORCE_REFLECTIONS ===
	#if !defined IPBR_ENABLED && !defined IPBR_FORCE_REFLECTIONS
		isMetalBlock = false;
	#endif

	// === Применяем эффекты для металлических блоков ===
	if (isMetalBlock) {
		smoothness *= reflectionSharpness;

		specularData.r = smoothness;
		specularData.g = metalness;
		specularData.b = 0.0;
		specularData.a = 0.0;

		albedo.rgb = mix(albedo.rgb, albedo.rgb * metalTint, 0.35);
		albedo.rgb = pow(albedo.rgb, vec3(0.90));

		vec3 viewDir = normalize(viewPos.xyz);
		float fresnel = pow(1.0 - abs(dot(tbnMatrix[2], viewDir)), 2.5);

		vec3 fresnelColor = vec3(0.15, 0.18, 0.25);
		if (materialIDs == 2011u) fresnelColor = vec3(0.25, 0.22, 0.15);
		else if (materialIDs >= 2012u && materialIDs <= 2055u) fresnelColor = vec3(0.25, 0.22, 0.15);
		else if (materialIDs == 2016u) fresnelColor = vec3(0.12, 0.12, 0.20);
		else if (materialIDs == 2064u) fresnelColor = vec3(0.22, 0.17, 0.13);

		fresnel *= mix(0.4, 1.0, reflectionSharpness);
		albedo.rgb += fresnel * fresnelColor * fresnelStrength * (1.0 - detailMask * 0.5);
	}

	#if defined IS_OVERWORLD
		if (wetnessCustom > 1e-2) {
			float noise = GetRainWetness(minecraftPos.xz - minecraftPos.y);
			noise *= remap(0.5, 0.9, (mat3(gbufferModelViewInverse) * tbnMatrix[2]).y);
			noise *= saturate(lightmap.y * 10.0 - 9.0);
			float wetFact = smoothstep(0.54, 0.62, noise);

			#ifdef RAIN_SPLASH_EFFECT
				normalData = mix(normalData.xyz, vec3(GetRainNormal(minecraftPos), 1.0), wetFact * 0.5);
			#else
				normalData = mix(normalData.xyz, vec3(0.0, 0.0, 1.0), wetFact);
			#endif

			wetFact = sqr(remap(0.35, 0.57, noise));

			#ifdef FORCE_WET_EFFECT
				if (!isMetalBlock) {
					specularData.r = mix(specularData.r, 1.0, wetFact);
					specularData.g = max(specularData.g, 0.04 * wetFact);
					specularData.rg += (dither - 0.5) * rcp(255.0);
				}
			#endif

			vec3 wetAlbedo = ColorSaturation(albedo.rgb, 0.75) * 0.85;
			#ifdef POROSITY
				float porosity = specularData.b > 64.5 / 255.0 ? 0.0 : remap(specularData.b, 0.0, 64.0 / 255.0) * 0.7;
				wetAlbedo *= oneMinus(porosity) / oneMinus(porosity * wetAlbedo);
			#endif
			if (!isMetalBlock) {
				albedo.rgb = mix(albedo.rgb, wetAlbedo, sqr(remap(0.3, 0.56, noise)));
			}
		}
	#endif

// ============================================================
// SSS — Subsurface Scattering для всей растительности
// ============================================================
#if TEXTURE_FORMAT == 0 && defined MC_SPECULAR_MAP
    #if SUBSERFACE_SCATTERING_MODE == 1
        // --- Короткая трава (ванильные спрайты + шейдерные лезвия) ---
        if (materialIDs == 6u || materialIDs == 65535u) specularData.b = max(0.45, specularData.b);

        // --- Листья (только если SHADER_LEAVES не задал своё значение) ---
        #if !defined(SHADER_LEAVES) || SHADER_LEAVES == 0
            if (materialIDs == 7u || materialIDs == 10u) specularData.b = max(0.7, specularData.b);
            if (materialIDs == 10u) {
                specularData.b = max(0.7, specularData.b);
                specularData.r = 0.85;
                specularData.g = 0.88;
                albedo.rgb = mix(albedo.rgb, albedo.rgb * vec3(1.0, 1.0, 1.0), 0.85);
            }
        #else
            // SHADER_LEAVES активен — листья (7u) уже обработаны выше
            if (materialIDs == 10u) specularData.b = max(0.7, specularData.b);
        #endif

    #elif SUBSERFACE_SCATTERING_MODE == 0
        // --- Короткая трава ---
        if (materialIDs == 6u || materialIDs == 65535u) specularData.b = 0.45;

        // --- Листья ---
        #if !defined(SHADER_LEAVES) || SHADER_LEAVES == 0
            if (materialIDs == 7u || materialIDs == 10u) specularData.b = 0.7;
            if (materialIDs == 10u) {
                specularData.b = 0.7;
                specularData.r = 0.85;
                specularData.g = 0.88;
                albedo.rgb = mix(albedo.rgb, albedo.rgb * vec3(1.0, 1.0, 1.0), 0.85);
            }
        #else
            if (materialIDs == 10u) specularData.b = 0.7;
        #endif
    #endif

#elif SUBSERFACE_SCATTERING_MODE < 2
    specularData.a = 0.0;

    // --- Короткая трава (ванильные спрайты 6u + шейдерные лезвия 65535u) ---
    if (materialIDs == 6u || materialIDs == 65535u) specularData.a = 0.45;

    // --- Листья ---
    #if !defined(SHADER_LEAVES) || SHADER_LEAVES == 0
        if (materialIDs == 7u || materialIDs == 10u) specularData.a = 0.7;
        if (materialIDs == 10u) {
            specularData.a = 0.7;
            specularData.r = 0.85;
            specularData.g = 0.88;
            albedo.rgb = mix(albedo.rgb, albedo.rgb * vec3(1.0, 1.0, 1.0), 0.85);
        }
    #else
        if (materialIDs == 10u) specularData.a = 0.7;
    #endif
#endif


	normalData = normalize(tbnMatrix * normalData);

	albedoData = albedo.rgb;

    #if LIGHT_OVERLAY == 1
        float blockLight = lightmap.x;
        float skyLight = lightmap.y;
        if (blockLight < 1.0 / 15.0) {
            vec2 uv = fract(minecraftPos.xz);
            float line1 = abs(uv.x - uv.y);
            float line2 = abs(uv.x + uv.y - 1.0);
            float thickness = 0.1;
            if (min(line1, line2) < thickness) {
                vec3 maskColor = (skyLight < 0.5 ? vec3(1.0, 0.0, 0.0) : vec3(1.0, 1.0, 0.0));
                albedoData = mix(albedoData, maskColor, 0.8);
            }
        }
    #endif

    #if CHUNK_OVERLAY == 1
        float modX = mod(minecraftPos.x, 16.0);
        float modZ = mod(minecraftPos.z, 16.0);
        bool borderX = (modX < 0.1 || modX > 15.9);
        bool borderZ = (modZ < 0.1 || modZ > 15.9);
        if (borderX || borderZ) {
            albedoData = mix(albedoData, vec3(1.0, 0.0, 0.0), 0.5);
        }
    #endif

	colortex7Out.xy = lightmap + (dither - 0.5) * rcp(255.0);
	// Cap materialIDs at 255 for colortex7 storage (8-bit); shader grass (65535u) stored as 6 (grass-like)
	uint storedMaterialID = materialIDs == 65535u ? 6u : min(materialIDs, 255u);
	colortex7Out.z = float(storedMaterialID + 0.1) * rcp(255.0);

	colortex3Out.xy = EncodeNormal(normalData);
	colortex3Out.z = PackUnorm2x8(specularData.rg);
	colortex3Out.w = PackUnorm2x8(specularData.ba);
}
