Рука и облака рендерятся неправильно при использовании моего шейдера

Версия Minecraft
1.12.2
API
Forge
51
8
5
Привет! Я Столкнулся с проблемой при создании маски сущностей (EntityMaskRenderer.java) для тепловизора (ThermalVisionRenderer.java). Возникают визуальные артефакты, связанные с рендером: Рука игрока иногда отображается белой и полупрозрачной. В некоторых случаях модель руки ломается (деформируется или отсутствует часть геометрии), а также освещение не применяется корректно — рука выглядит "плоской" и не освещается, как должна. Облака при включённой маске и виде от третьего лица становятся слишком белыми, даже ночью, когда должны быть тёмными. Когда я взлетаю выше облаков все блоки снизу начинают выглядеть очень странно... Пожалуйста, помогите решить проблемы с рендерингом, уже неделю мучаюсь
1753465722112.png

1753465743902.png
1753465928118.png
1753466860724.png
Java:
package com.afp.handlers;

import com.afp.shaders.EntityMaskRenderer;
import com.afp.shaders.NightVisionRenderer;
import com.afp.shaders.ThermalVisionRenderer;
import net.minecraft.client.Minecraft;
import net.minecraft.item.ItemStack;
import net.minecraft.nbt.NBTTagCompound;
import net.minecraftforge.client.event.RenderWorldLastEvent;
import net.minecraftforge.fml.common.eventhandler.SubscribeEvent;
import net.minecraftforge.fml.relauncher.Side;
import net.minecraftforge.fml.relauncher.SideOnly;

@SideOnly(Side.CLIENT)
public class RenderWorldLastEventHandler {
    // Кэшируем инстансы рендереров для уменьшения нагрузки на GC
    private final ThermalVisionRenderer thermalVisionRenderer = new ThermalVisionRenderer();
    private final NightVisionRenderer nightVisionRenderer = new NightVisionRenderer();
    private final Minecraft mc = Minecraft.getMinecraft();

    // Кэшируем строки для сравнения
    private static final String THERMAL_VISION = "thermal_vision";
    private static final String NIGHT_VISION = "night_vision";

    @SubscribeEvent
    public void onRenderWorldLast(RenderWorldLastEvent event) {
        // Ранний выход при отсутствии необходимых условий
        if (MC.player == null || MC.world == null) return;

        // Получаем шлем из слота 3 (голова)
        final ItemStack helmet = MC.player.inventory.armorInventory.get(3);
        if (helmet.isEmpty() || !helmet.hasTagCompound()) return;

        // Извлекаем тег видения из NBT
        final NBTTagCompound tag = helmet.getTagCompound();
        String visionTag = tag.getString("vision");

        // Выбираем тип рендеринга на основе тега
        if (THERMAL_VISION.equals(visionTag)) {
            EntityMaskRenderer.renderEntityMasks(event.getPartialTicks());
            thermalVisionRenderer.render();
        } else if (NIGHT_VISION.equals(visionTag)) {
            nightVisionRenderer.render();
        }
    }
}
Java:
package com.afp.shaders;

import com.afp.entities.EntityExoskeleton;
import net.minecraft.client.Minecraft;
import net.minecraft.client.renderer.GlStateManager;
import net.minecraft.client.renderer.OpenGlHelper;
import net.minecraft.client.renderer.entity.Render;
import net.minecraft.client.renderer.entity.RenderManager;
import net.minecraft.client.shader.Framebuffer;
import net.minecraft.entity.Entity;
import net.minecraft.entity.EntityLivingBase;
import net.minecraft.entity.item.EntityArmorStand;
import net.minecraft.entity.item.EntityBoat;
import net.minecraft.entity.item.EntityItem;
import net.minecraftforge.fml.relauncher.Side;
import net.minecraftforge.fml.relauncher.SideOnly;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL30;

import java.util.List;

@SideOnly(Side.CLIENT)
public class EntityMaskRenderer {
    private static final Minecraft MC = Minecraft.getMinecraft();
    private static Framebuffer maskFramebuffer;

    // Кэшируем часто используемые классы
    private static final Class<?> EXOSKELETON_CLASS = EntityExoskeleton.Exoskeleton.class;
    private static final Class<?> ARMOR_STAND_CLASS = EntityArmorStand.class;

    public static boolean shouldRenderMask(Entity entity) {
        Class<?> entityClass = entity.getClass();
        return entity instanceof EntityLivingBase &&
                entityClass != EXOSKELETON_CLASS &&
                entityClass != ARMOR_STAND_CLASS;
    }

    private static void ensureMaskBufferInitialized() {
        if (maskFramebuffer == null) {
            maskFramebuffer = new Framebuffer(MC.displayWidth, MC.displayHeight, true);
            maskFramebuffer.setFramebufferColor(0, 0, 0, 0);
        } else if (maskFramebuffer.framebufferWidth != MC.displayWidth ||
                maskFramebuffer.framebufferHeight != MC.displayHeight) {
            maskFramebuffer.createBindFramebuffer(MC.displayWidth, MC.displayHeight);
        }
    }

    public static void renderEntityMasks(float partialTicks) {
        if (MC.player == null || MC.world == null) return;
        ensureMaskBufferInitialized();
        maskFramebuffer.bindFramebuffer(false);

        // Очистка буфера цвета
        GlStateManager.clearColor(0, 0, 0, 0);
        GlStateManager.clear(GL11.GL_COLOR_BUFFER_BIT);

        // Копирование буфера глубины
        OpenGlHelper.glBindFramebuffer(GL30.GL_READ_FRAMEBUFFER, MC.getFramebuffer().framebufferObject);
        OpenGlHelper.glBindFramebuffer(GL30.GL_DRAW_FRAMEBUFFER, maskFramebuffer.framebufferObject);
        GL30.glBlitFramebuffer(0, 0, MC.displayWidth, MC.displayHeight, 0, 0, MC.displayWidth, MC.displayHeight, GL11.GL_DEPTH_BUFFER_BIT, GL11.GL_NEAREST);

        // Настройка состояний рендеринга
        OpenGlHelper.glBindFramebuffer(OpenGlHelper.GL_FRAMEBUFFER, maskFramebuffer.framebufferObject);
        GlStateManager.enableDepth();
        GlStateManager.depthMask(false);
        GlStateManager.disableBlend();
        GlStateManager.disableLighting();
        GlStateManager.color(1.0f, 1.0f, 1.0f, 1.0f);

        // Рендеринг сущностей
        Entity viewEntity = MC.getRenderViewEntity();
        RenderManager renderManager = MC.getRenderManager();
        List<Entity> entities = MC.world.loadedEntityList;
        for (Entity entity : entities) {
            if (entity != viewEntity && shouldRenderMask(entity)) {
                renderEntitySilhouette(entity, partialTicks, renderManager);
            }
        }

        // Восстановление состояний
        GlStateManager.depthMask(true);
        GlStateManager.disableDepth();
        GlStateManager.enableLighting();
        MC.getFramebuffer().bindFramebuffer(false);
    }

    private static void renderEntitySilhouette(Entity entity, float partialTicks, RenderManager renderManager) {
        // Интерполяция позиции
        double x = entity.lastTickPosX + (entity.posX - entity.lastTickPosX) * partialTicks;
        double y = entity.lastTickPosY + (entity.posY - entity.lastTickPosY) * partialTicks;
        double z = entity.lastTickPosZ + (entity.posZ - entity.lastTickPosZ) * partialTicks;

        // Перевод в пространство камеры
        double renderX = x - renderManager.viewerPosX;
        double renderY = y - renderManager.viewerPosY;
        double renderZ = z - renderManager.viewerPosZ;

        GlStateManager.pushMatrix();
        GlStateManager.translate(renderX, renderY, renderZ);
        renderEntityModel(entity, renderManager, partialTicks);
        GlStateManager.popMatrix();
    }

    private static void renderEntityModel(Entity entity, RenderManager renderManager, float partialTicks) {
        GlStateManager.pushAttrib();
        Render<Entity> renderer = renderManager.getEntityRenderObject(entity);
        if (renderer != null) {
            // Интерполяция вращения
            float interpolatedYaw = interpolateRotation(entity.prevRotationYaw, entity.rotationYaw, partialTicks);
            renderer.doRender(entity, 0, 0, 0, interpolatedYaw, partialTicks);
        }
        GlStateManager.popAttrib();
    }

    private static float interpolateRotation(float prevRotation, float currentRotation, float partialTicks) {
        float delta = currentRotation - prevRotation;
        // Нормализация угла
        delta = (delta + 180) % 360 - 180;
        return prevRotation + partialTicks * delta;
    }

    public static int getMaskTexture() {
        return maskFramebuffer != null ? maskFramebuffer.framebufferTexture : -1;
    }
}
Java:
#version 120

uniform sampler2D DiffuseSampler;
uniform sampler2D NoiseSampler;
uniform sampler2D EntityMask;
uniform float VignetteRadius;
uniform float Brightness;
uniform float Time;
uniform float NoiseAmplification;
uniform float Contrast;
uniform vec2 InSize;

varying vec2 texCoord;

void main() {
    // Чтение исходного цвета
    vec4 texColor = texture2D(DiffuseSampler, texCoord);
    vec4 mask = texture2D(EntityMask, texCoord.xy);

    if (mask.a > 0.01) {
        // Белый силуэт для сущностей
        gl_FragColor = vec4(1.0, 1.0, 1.0, 1.0);
        return;
    }

    // Генерация анимированного шума
    vec2 uv;
    uv.x = 0.35 * sin(Time * 10.0);
    uv.y = 0.35 * cos(Time * 10.0);
    vec3 noise = texture2D(NoiseSampler, texCoord.xy + uv).rgb * NoiseAmplification;

    // Применение шума к цвету
    texColor.rg += noise.rg * 0.05;

    // Применяем яркость
    texColor.rgb *= Brightness;

    // Применение виньетирования
    float dist = distance(texCoord, vec2(0.5));
    float vignette = smoothstep(
        VignetteRadius,
        VignetteRadius - 0.25,
        dist
    );
    texColor.rgb *= vignette;

    // Переход в grayscale
    float intensity = dot(texColor.rgb, vec3(0.299, 0.587, 0.114));

    // Коррекция контраста
    intensity = Contrast * (intensity - 0.5) + 0.5;
    intensity = clamp(intensity, 0.0, 1.0);

    //Формирование серого цвета
    vec3 finalColor = vec3(intensity);

    gl_FragColor = vec4(finalColor, 1.0);
}
Java:
package com.afp.shaders;

import net.minecraft.client.Minecraft;
import net.minecraft.client.renderer.BufferBuilder;
import net.minecraft.client.renderer.GlStateManager;
import net.minecraft.client.renderer.OpenGlHelper;
import net.minecraft.client.renderer.Tessellator;
import net.minecraft.client.renderer.vertex.DefaultVertexFormats;
import net.minecraft.client.shader.Framebuffer;
import net.minecraft.util.ResourceLocation;
import net.minecraftforge.fml.relauncher.Side;
import net.minecraftforge.fml.relauncher.SideOnly;
import org.lwjgl.BufferUtils;
import org.lwjgl.opengl.ARBShaderObjects;
import org.lwjgl.opengl.GL11;

import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import java.io.InputStream;
import java.nio.ByteBuffer;
import java.util.HashMap;
import java.util.Map;

import static org.lwjgl.opengl.GL11.*;

@SideOnly(Side.CLIENT)
public abstract class BaseShaderRenderer {
    protected static final Minecraft MC = Minecraft.getMinecraft();

    // Статическая шумовая текстура (общая для всех шейдеров)
    private static int noiseTextureId = -1;
    private static boolean noiseTextureLoaded = false;

    protected Framebuffer screenBuffer;
    protected ShaderManager shaderManager;
    protected boolean initializationFailed = false;
    protected final Map<String, Integer> uniformCache = new HashMap<>(8);

    protected abstract ResourceLocation getVertexShaderLocation();
    protected abstract ResourceLocation getFragmentShaderLocation();
    protected abstract void setupUniforms(int width, int height);

    public void render() {
        // Ранний выход при ошибке инициализации
        if (initializationFailed) return;

        if (shaderManager == null) {
            initializeResources();
            if (initializationFailed) return;
        }

        final int width = MC.displayWidth;
        final int height = MC.displayHeight;

        // Ресайз фреймбуфера при изменении размеров экрана
        if (screenBuffer == null || screenBuffer.framebufferWidth != width || screenBuffer.framebufferHeight != height) {
            resizeFrameBuffer(width, height);
        }

        // Копируем текущий буфер в текстуру
        GlStateManager.bindTexture(screenBuffer.framebufferTexture);
        GL11.glCopyTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 0, 0, width, height);

        // Настройка состояния рендера
        GlStateManager.pushMatrix();
        GlStateManager.pushAttrib();
        setupRenderState(width, height);

        // Активация шейдера
        shaderManager.bind();
        setupUniforms(width, height);

        // Рендер полноэкранного квада
        renderFullscreenQuad(width, height);

        // Восстановление состояния
        shaderManager.unbind();
        restoreRenderState();
        GlStateManager.popAttrib();
        GlStateManager.popMatrix();
    }

    protected void initializeResources() {
        if (!OpenGlHelper.shadersSupported) {
            initializationFailed = true;
            return;
        }

        try {
            shaderManager = new ShaderManager(getVertexShaderLocation(), getFragmentShaderLocation());
            loadNoiseTexture();
        } catch (Exception e) {
            initializationFailed = true;
            System.err.println("Shader initialization failed");
            e.printStackTrace();
        }
    }

    protected synchronized void loadNoiseTexture() {
        if (noiseTextureLoaded) return;

        try {
            ResourceLocation noiseRes = new ResourceLocation("afp", "textures/misc/noise.png");
            try (InputStream in = MC.getResourceManager().getResource(noiseRes).getInputStream()) {
                BufferedImage image = ImageIO.read(in);
                int width = image.getWidth();
                int height = image.getHeight();
                int[] pixels = new int[width * height];
                image.getRGB(0, 0, width, height, pixels, 0, width);

                ByteBuffer buffer = BufferUtils.createByteBuffer(width * height * 4);
                for (int y = 0; y &lt; height; y++) {
                    for (int x = 0; x &lt; width; x++) {
                        int pixel = pixels[y * width + x];
                        buffer.put((byte) ((pixel &gt;&gt; 16) &amp; 0xFF));
                        buffer.put((byte) ((pixel &gt;&gt; 8) &amp; 0xFF));
                        buffer.put((byte) (pixel &amp; 0xFF));
                        buffer.put((byte) ((pixel &gt;&gt; 24) &amp; 0xFF));
                    }
                }
                buffer.flip();

                noiseTextureId = GlStateManager.generateTexture();
                GlStateManager.bindTexture(noiseTextureId);
                GlStateManager.glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
                GlStateManager.glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
                GlStateManager.glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
                GlStateManager.glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
                GL11.glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, buffer);
            }
            noiseTextureLoaded = true;
        } catch (Exception e) {
            initializationFailed = true;
            System.err.println("Failed to load noise texture");
            e.printStackTrace();        }
    }

    protected void resizeFrameBuffer(int width, int height) {
        if (screenBuffer != null) {
            screenBuffer.deleteFramebuffer();
        }
        screenBuffer = new Framebuffer(width, height, false);
        screenBuffer.setFramebufferFilter(GL_NEAREST);
        screenBuffer.createFramebuffer(width, height);
    }

    protected void setupRenderState(int width, int height) {
        GlStateManager.disableDepth();
        GlStateManager.depthMask(false);
        GlStateManager.matrixMode(GL_PROJECTION);
        GlStateManager.pushMatrix();
        GlStateManager.loadIdentity();
        GlStateManager.ortho(0, width, height, 0, -1, 1);
        GlStateManager.matrixMode(GL_MODELVIEW);
        GlStateManager.pushMatrix();
        GlStateManager.loadIdentity();
    }

    protected void cacheUniformLocation(String name, int textureUnit) {
        Integer location = uniformCache.get(name);
        if (location == null) {
            location = shaderManager.getUniformLocation(name);
            uniformCache.put(name, location);
        }
        if (location != null && location >= 0) {
            ARBShaderObjects.glUniform1iARB(location, textureUnit);
        }
    }

    protected void setUniformFloat(String name, float value) {
        Integer location = uniformCache.get(name);
        if (location == null) {
            location = shaderManager.getUniformLocation(name);
            uniformCache.put(name, location);
        }
        if (location != null && location >= 0) {
            ARBShaderObjects.glUniform1fARB(location, value);
        }
    }

    protected void setUniformVec2(String name, float x, float y) {
        Integer location = uniformCache.get(name);
        if (location == null) {
            location = shaderManager.getUniformLocation(name);
            uniformCache.put(name, location);
        }
        if (location != null && location >= 0) {
            ARBShaderObjects.glUniform2fARB(location, x, y);
        }
    }

    protected void renderFullscreenQuad(int width, int height) {
        Tessellator tessellator = Tessellator.getInstance();
        BufferBuilder buffer = tessellator.getBuffer();

        buffer.begin(GL_QUADS, DefaultVertexFormats.POSITION_TEX);
        buffer.pos(0, height, 0).tex(0, 0).endVertex();
        buffer.pos(width, height, 0).tex(1, 0).endVertex();
        buffer.pos(width, 0, 0).tex(1, 1).endVertex();
        buffer.pos(0, 0, 0).tex(0, 1).endVertex();
        tessellator.draw();
    }

    protected void restoreRenderState() {
        GlStateManager.matrixMode(GL_PROJECTION);
        GlStateManager.popMatrix();
        GlStateManager.matrixMode(GL_MODELVIEW);
        GlStateManager.popMatrix();
        GlStateManager.depthMask(true);
        GlStateManager.enableDepth();
        GlStateManager.setActiveTexture(33985);
        GlStateManager.bindTexture(0);
        GlStateManager.setActiveTexture(33984);
    }

    public static int getNoiseTexture() {
        return noiseTextureId;
    }
}
Java:
package com.afp.shaders;

import net.minecraft.client.Minecraft;
import net.minecraft.client.renderer.OpenGlHelper;
import net.minecraft.util.ResourceLocation;
import org.apache.commons.io.IOUtils;
import org.lwjgl.BufferUtils;

import java.io.InputStream;
import java.nio.ByteBuffer;
import java.nio.charset.StandardCharsets;

public class ShaderManager {
    private final int programId;
    private boolean isValid;

    public ShaderManager(ResourceLocation vertexShaderLoc, ResourceLocation fragmentShaderLoc) {
        if (!OpenGlHelper.shadersSupported) {
            programId = 0;
            isValid = false;
            return;
        }

        int vertexShader = 0;
        int fragmentShader = 0;
        programId = OpenGlHelper.glCreateProgram();

        try {
            // Компиляция вершинного шейдера
            vertexShader = compileShader(vertexShaderLoc, OpenGlHelper.GL_VERTEX_SHADER);
            OpenGlHelper.glAttachShader(programId, vertexShader);

            // Компиляция фрагментного шейдера
            fragmentShader = compileShader(fragmentShaderLoc, OpenGlHelper.GL_FRAGMENT_SHADER);
            OpenGlHelper.glAttachShader(programId, fragmentShader);

            // Линковка программы
            OpenGlHelper.glLinkProgram(programId);
            validateProgram();
            isValid = true;
        } catch (Exception e) {
            System.err.println("Shader compilation failed");
            e.printStackTrace();
            isValid = false;
        } finally {
            // Очистка ресурсов
            if (vertexShader != 0) OpenGlHelper.glDeleteShader(vertexShader);
            if (fragmentShader != 0) OpenGlHelper.glDeleteShader(fragmentShader);
        }
    }

    private int compileShader(ResourceLocation location, int shaderType) throws Exception {
        String source = loadResource(location);
        int shader = OpenGlHelper.glCreateShader(shaderType);

        byte[] sourceBytes = source.getBytes(StandardCharsets.UTF_8);
        ByteBuffer buffer = BufferUtils.createByteBuffer(sourceBytes.length + 1);
        buffer.put(sourceBytes);
        buffer.put((byte) 0);
        buffer.flip();

        OpenGlHelper.glShaderSource(shader, buffer);
        OpenGlHelper.glCompileShader(shader);
        validateShader(shader, shaderType);
        return shader;
    }

    private String loadResource(ResourceLocation location) throws Exception {
        try (InputStream in = Minecraft.getMinecraft().getResourceManager().getResource(location).getInputStream()) {
            return IOUtils.toString(in, StandardCharsets.UTF_8);
        }
    }

    private void validateShader(int shader, int shaderType) {
        if (OpenGlHelper.glGetShaderi(shader, OpenGlHelper.GL_COMPILE_STATUS) == 0) {
            String type = shaderType == OpenGlHelper.GL_VERTEX_SHADER ? "Vertex" : "Fragment";
            String log = OpenGlHelper.glGetShaderInfoLog(shader, 1024);
            throw new RuntimeException(type + " shader error: " + log);
        }
    }

    private void validateProgram() {
        if (OpenGlHelper.glGetProgrami(programId, OpenGlHelper.GL_LINK_STATUS) == 0) {
            String log = OpenGlHelper.glGetProgramInfoLog(programId, 1024);
            throw new RuntimeException("Shader link error: " + log);
        }
    }

    public void bind() {
        if (isValid) OpenGlHelper.glUseProgram(programId);
    }

    public void unbind() {
        OpenGlHelper.glUseProgram(0);
    }

    public int getUniformLocation(String name) {
        return isValid ? OpenGlHelper.glGetUniformLocation(programId, name) : -1;
    }

    public boolean isValid() {
        return isValid;
    }
}
 
Последнее редактирование:
// Восстановление состояний GlStateManager.depthMask(true); GlStateManager.disableDepth(); GlStateManager.enableLighting();
судя по тому что у тя рука просвечивется, вероятно, нужно убрать GlStateManager.disableDepth()
тест глубины по идее никогда не нужно выключать по умолчанию

~~~

суть GlStateManager в том чтобы изменять состояния только если оно не равно предыдущему, чтобы уменьшить обращения к видеокарте или типа
исходя из этого кажется наиболее рациональным любому рендеру следовать следующей конвенции:
  • перед рендером через GlStateManager выставлять полный стейт какой нужно, игнорируя любые ожидания о предыдущем стейте(потому что если он отличается, то вправится как надо, а если не отличается, то ничо не произойдет)
  • после рендера не восстанавливать стейт(потому что следующий рендер настроит себе как надо, а часть или весь стейт может быть таким каким нужно следующему рендеру)
GlStateManager это ванильный класс. Кто-нить знает почему большинство кода ванильного рендера не следует вышеприведенной конвенции? Отличается ли ситуация в новых версиях? Типо, рефакторят по-немногу
 
Назад
Сверху