package net.minecraft.client.renderer; import com.google.common.collect.Lists; import com.mojang.blaze3d.shaders.FogShape; import java.util.List; import net.fabricmc.api.EnvType; import net.fabricmc.api.Environment; import net.minecraft.Util; import net.minecraft.client.Camera; import net.minecraft.client.multiplayer.ClientLevel; import net.minecraft.client.player.LocalPlayer; import net.minecraft.core.BlockPos; import net.minecraft.core.Holder; import net.minecraft.tags.BiomeTags; import net.minecraft.util.ARGB; import net.minecraft.util.CubicSampler; import net.minecraft.util.Mth; import net.minecraft.world.effect.MobEffect; import net.minecraft.world.effect.MobEffectInstance; import net.minecraft.world.effect.MobEffects; import net.minecraft.world.entity.Entity; import net.minecraft.world.entity.LivingEntity; import net.minecraft.world.level.biome.Biome; import net.minecraft.world.level.biome.BiomeManager; import net.minecraft.world.level.material.FogType; import net.minecraft.world.phys.Vec3; import org.jetbrains.annotations.Nullable; import org.joml.Vector3f; import org.joml.Vector4f; @Environment(EnvType.CLIENT) public class FogRenderer { private static final int WATER_FOG_DISTANCE = 96; private static final List MOB_EFFECT_FOG = Lists.newArrayList( new FogRenderer.BlindnessFogFunction(), new FogRenderer.DarknessFogFunction() ); public static final float BIOME_FOG_TRANSITION_TIME = 5000.0F; private static int targetBiomeFog = -1; private static int previousBiomeFog = -1; private static long biomeChangedTime = -1L; private static boolean fogEnabled = true; public static Vector4f computeFogColor(Camera camera, float partialTick, ClientLevel level, int renderDistance, float darkenWorldAmount) { FogType fogType = camera.getFluidInCamera(); Entity entity = camera.getEntity(); float r; float s; float t; if (fogType == FogType.WATER) { long l = Util.getMillis(); int i = level.getBiome(BlockPos.containing(camera.getPosition())).value().getWaterFogColor(); if (biomeChangedTime < 0L) { targetBiomeFog = i; previousBiomeFog = i; biomeChangedTime = l; } int j = targetBiomeFog >> 16 & 0xFF; int k = targetBiomeFog >> 8 & 0xFF; int m = targetBiomeFog & 0xFF; int n = previousBiomeFog >> 16 & 0xFF; int o = previousBiomeFog >> 8 & 0xFF; int p = previousBiomeFog & 0xFF; float f = Mth.clamp((float)(l - biomeChangedTime) / 5000.0F, 0.0F, 1.0F); float g = Mth.lerp(f, (float)n, (float)j); float h = Mth.lerp(f, (float)o, (float)k); float q = Mth.lerp(f, (float)p, (float)m); r = g / 255.0F; s = h / 255.0F; t = q / 255.0F; if (targetBiomeFog != i) { targetBiomeFog = i; previousBiomeFog = Mth.floor(g) << 16 | Mth.floor(h) << 8 | Mth.floor(q); biomeChangedTime = l; } } else if (fogType == FogType.LAVA) { r = 0.6F; s = 0.1F; t = 0.0F; biomeChangedTime = -1L; } else if (fogType == FogType.POWDER_SNOW) { r = 0.623F; s = 0.734F; t = 0.785F; biomeChangedTime = -1L; } else { float u = 0.25F + 0.75F * renderDistance / 32.0F; u = 1.0F - (float)Math.pow(u, 0.25); int v = level.getSkyColor(camera.getPosition(), partialTick); float w = ARGB.redFloat(v); float x = ARGB.greenFloat(v); float y = ARGB.blueFloat(v); float z = Mth.clamp(Mth.cos(level.getTimeOfDay(partialTick) * (float) (Math.PI * 2)) * 2.0F + 0.5F, 0.0F, 1.0F); BiomeManager biomeManager = level.getBiomeManager(); Vec3 vec3 = camera.getPosition().subtract(2.0, 2.0, 2.0).scale(0.25); Vec3 vec32 = CubicSampler.gaussianSampleVec3( vec3, (ix, j, k) -> level.effects().getBrightnessDependentFogColor(Vec3.fromRGB24(biomeManager.getNoiseBiomeAtQuart(ix, j, k).value().getFogColor()), z) ); r = (float)vec32.x(); s = (float)vec32.y(); t = (float)vec32.z(); if (renderDistance >= 4) { float f = Mth.sin(level.getSunAngle(partialTick)) > 0.0F ? -1.0F : 1.0F; Vector3f vector3f = new Vector3f(f, 0.0F, 0.0F); float h = camera.getLookVector().dot(vector3f); if (h < 0.0F) { h = 0.0F; } if (h > 0.0F && level.effects().isSunriseOrSunset(level.getTimeOfDay(partialTick))) { int aa = level.effects().getSunriseOrSunsetColor(level.getTimeOfDay(partialTick)); h *= ARGB.alphaFloat(aa); r = r * (1.0F - h) + ARGB.redFloat(aa) * h; s = s * (1.0F - h) + ARGB.greenFloat(aa) * h; t = t * (1.0F - h) + ARGB.blueFloat(aa) * h; } } r += (w - r) * u; s += (x - s) * u; t += (y - t) * u; float fx = level.getRainLevel(partialTick); if (fx > 0.0F) { float g = 1.0F - fx * 0.5F; float hx = 1.0F - fx * 0.4F; r *= g; s *= g; t *= hx; } float g = level.getThunderLevel(partialTick); if (g > 0.0F) { float hx = 1.0F - g * 0.5F; r *= hx; s *= hx; t *= hx; } biomeChangedTime = -1L; } float ux = ((float)camera.getPosition().y - level.getMinY()) * level.getLevelData().getClearColorScale(); FogRenderer.MobEffectFogFunction mobEffectFogFunction = getPriorityFogFunction(entity, partialTick); if (mobEffectFogFunction != null) { LivingEntity livingEntity = (LivingEntity)entity; ux = mobEffectFogFunction.getModifiedVoidDarkness(livingEntity, livingEntity.getEffect(mobEffectFogFunction.getMobEffect()), ux, partialTick); } if (ux < 1.0F && fogType != FogType.LAVA && fogType != FogType.POWDER_SNOW) { if (ux < 0.0F) { ux = 0.0F; } ux *= ux; r *= ux; s *= ux; t *= ux; } if (darkenWorldAmount > 0.0F) { r = r * (1.0F - darkenWorldAmount) + r * 0.7F * darkenWorldAmount; s = s * (1.0F - darkenWorldAmount) + s * 0.6F * darkenWorldAmount; t = t * (1.0F - darkenWorldAmount) + t * 0.6F * darkenWorldAmount; } float wx; if (fogType == FogType.WATER) { if (entity instanceof LocalPlayer) { wx = ((LocalPlayer)entity).getWaterVision(); } else { wx = 1.0F; } } else if (entity instanceof LivingEntity livingEntity2 && livingEntity2.hasEffect(MobEffects.NIGHT_VISION) && !livingEntity2.hasEffect(MobEffects.DARKNESS)) { wx = GameRenderer.getNightVisionScale(livingEntity2, partialTick); } else { wx = 0.0F; } if (r != 0.0F && s != 0.0F && t != 0.0F) { float xx = Math.min(1.0F / r, Math.min(1.0F / s, 1.0F / t)); r = r * (1.0F - wx) + r * xx * wx; s = s * (1.0F - wx) + s * xx * wx; t = t * (1.0F - wx) + t * xx * wx; } return new Vector4f(r, s, t, 1.0F); } public static boolean toggleFog() { return fogEnabled = !fogEnabled; } @Nullable private static FogRenderer.MobEffectFogFunction getPriorityFogFunction(Entity entity, float partialTick) { return entity instanceof LivingEntity livingEntity ? (FogRenderer.MobEffectFogFunction)MOB_EFFECT_FOG.stream() .filter(mobEffectFogFunction -> mobEffectFogFunction.isEnabled(livingEntity, partialTick)) .findFirst() .orElse(null) : null; } public static FogParameters setupFog(Camera camera, FogRenderer.FogMode fogMode, Vector4f fogColor, float renderDistance, boolean isFoggy, float partialTick) { if (!fogEnabled) { return FogParameters.NO_FOG; } else { FogType fogType = camera.getFluidInCamera(); Entity entity = camera.getEntity(); FogRenderer.FogData fogData = new FogRenderer.FogData(fogMode); FogRenderer.MobEffectFogFunction mobEffectFogFunction = getPriorityFogFunction(entity, partialTick); if (fogType == FogType.LAVA) { if (entity.isSpectator()) { fogData.start = -8.0F; fogData.end = renderDistance * 0.5F; } else if (entity instanceof LivingEntity && ((LivingEntity)entity).hasEffect(MobEffects.FIRE_RESISTANCE)) { fogData.start = 0.0F; fogData.end = 5.0F; } else { fogData.start = 0.25F; fogData.end = 1.0F; } } else if (fogType == FogType.POWDER_SNOW) { if (entity.isSpectator()) { fogData.start = -8.0F; fogData.end = renderDistance * 0.5F; } else { fogData.start = 0.0F; fogData.end = 2.0F; } } else if (mobEffectFogFunction != null) { LivingEntity livingEntity = (LivingEntity)entity; MobEffectInstance mobEffectInstance = livingEntity.getEffect(mobEffectFogFunction.getMobEffect()); if (mobEffectInstance != null) { mobEffectFogFunction.setupFog(fogData, livingEntity, mobEffectInstance, renderDistance, partialTick); } } else if (fogType == FogType.WATER) { fogData.start = -8.0F; fogData.end = 96.0F; if (entity instanceof LocalPlayer localPlayer) { fogData.end = fogData.end * Math.max(0.25F, localPlayer.getWaterVision()); Holder holder = localPlayer.level().getBiome(localPlayer.blockPosition()); if (holder.is(BiomeTags.HAS_CLOSER_WATER_FOG)) { fogData.end *= 0.85F; } } if (fogData.end > renderDistance) { fogData.end = renderDistance; fogData.shape = FogShape.CYLINDER; } } else if (isFoggy) { fogData.start = renderDistance * 0.05F; fogData.end = Math.min(renderDistance, 192.0F) * 0.5F; } else if (fogMode == FogRenderer.FogMode.FOG_SKY) { fogData.start = 0.0F; fogData.end = renderDistance; fogData.shape = FogShape.CYLINDER; } else if (fogMode == FogRenderer.FogMode.FOG_TERRAIN) { float f = Mth.clamp(renderDistance / 10.0F, 4.0F, 64.0F); fogData.start = renderDistance - f; fogData.end = renderDistance; fogData.shape = FogShape.CYLINDER; } return new FogParameters(fogData.start, fogData.end, fogData.shape, fogColor.x, fogColor.y, fogColor.z, fogColor.w); } } @Environment(EnvType.CLIENT) static class BlindnessFogFunction implements FogRenderer.MobEffectFogFunction { @Override public Holder getMobEffect() { return MobEffects.BLINDNESS; } @Override public void setupFog(FogRenderer.FogData fogData, LivingEntity entity, MobEffectInstance effectInstance, float farPlaneDistance, float partialTick) { float f = effectInstance.isInfiniteDuration() ? 5.0F : Mth.lerp(Math.min(1.0F, effectInstance.getDuration() / 20.0F), farPlaneDistance, 5.0F); if (fogData.mode == FogRenderer.FogMode.FOG_SKY) { fogData.start = 0.0F; fogData.end = f * 0.8F; } else if (fogData.mode == FogRenderer.FogMode.FOG_TERRAIN) { fogData.start = f * 0.25F; fogData.end = f; } } } @Environment(EnvType.CLIENT) static class DarknessFogFunction implements FogRenderer.MobEffectFogFunction { @Override public Holder getMobEffect() { return MobEffects.DARKNESS; } @Override public void setupFog(FogRenderer.FogData fogData, LivingEntity entity, MobEffectInstance effectInstance, float farPlaneDistance, float partialTick) { float f = Mth.lerp(effectInstance.getBlendFactor(entity, partialTick), farPlaneDistance, 15.0F); fogData.start = switch (fogData.mode) { case FOG_SKY -> 0.0F; case FOG_TERRAIN -> f * 0.75F; }; fogData.end = f; } @Override public float getModifiedVoidDarkness(LivingEntity entity, MobEffectInstance effectInstance, float voidDarkness, float partialTick) { return 1.0F - effectInstance.getBlendFactor(entity, partialTick); } } @Environment(EnvType.CLIENT) static class FogData { public final FogRenderer.FogMode mode; public float start; public float end; public FogShape shape = FogShape.SPHERE; public FogData(FogRenderer.FogMode mode) { this.mode = mode; } } @Environment(EnvType.CLIENT) public static enum FogMode { FOG_SKY, FOG_TERRAIN; } @Environment(EnvType.CLIENT) interface MobEffectFogFunction { Holder getMobEffect(); void setupFog(FogRenderer.FogData fogData, LivingEntity entity, MobEffectInstance effectInstance, float farPlaneDistance, float partialTick); default boolean isEnabled(LivingEntity entity, float partialTick) { return entity.hasEffect(this.getMobEffect()); } default float getModifiedVoidDarkness(LivingEntity entity, MobEffectInstance effectInstance, float voidDarkness, float partialTick) { MobEffectInstance mobEffectInstance = entity.getEffect(this.getMobEffect()); if (mobEffectInstance != null) { if (mobEffectInstance.endsWithin(19)) { voidDarkness = 1.0F - mobEffectInstance.getDuration() / 20.0F; } else { voidDarkness = 0.0F; } } return voidDarkness; } } }