Mohammed Baig
Mohammed Baig

Reputation: 1

OpenGL Texture rendering over another with 3D Batch Renderer

So, my current OpenGL Renderer seems to draw Texture B with Texture A for some reason. I checked the slots in RenderDoc and they seem to be properly bound with glBindTexture() and glActiveTexture(). I'm not quite sure what's happening. I've observed that when not moving the camera at all, the issue seems to disappear.

Screenshot

This is the main class:

package demo;

import org.joml.Matrix4f;
import org.lwjgl.opengl.GL46;
import rebel.graphics.*;

public class Test3D {
    public static void main(String[] args) {
        Window window = new Window(1000, 600, "");
        Renderer3D renderer3D = new Renderer3D(window.getWidth(), window.getHeight(), true);


        Texture2D a = new Texture2D("project/logo.png");
        Texture2D b = new Texture2D("project/texture.png");


        float rotX = 0f, rotY = 0f, rotZ = 0f;

        while(!window.shouldClose()){
            renderer3D.clear(Color.WHITE);


            renderer3D.setOrigin(0, 0);
            renderer3D.drawTexture(0.5f / -2f, 0.5f / -2f, 0.5f, 0.5f, a, Color.RED);
            renderer3D.drawTexture((0.5f / -2f) + 0.5f, 0.5f / -2f, 1f, 1f, b, Color.BLUE);
            renderer3D.resetTransform();



            renderer3D.getCamera().getViewMatrix().rotate((float) Math.toRadians(rotX), 1, 0, 0);
            renderer3D.getCamera().getViewMatrix().rotate((float) Math.toRadians(rotY), 0, 1, 0);
            renderer3D.getCamera().getViewMatrix().translate(0, 0, 1f);


            rotX = 360 * -(window.getMouseY() / window.getHeight());
            rotY = 360 * (window.getMouseX() / window.getWidth());




            renderer3D.updateCamera2D();
            renderer3D.render();
            renderer3D.getCamera().getViewMatrix().set(new Matrix4f().identity());

            System.out.println(GL46.glGetError());


            window.update();
        }

        window.close();


    }
}

And Renderer3D:

package rebel.graphics;

import org.joml.*;
import org.lwjgl.BufferUtils;
import rebel.FileReader;

import java.lang.Math;
import java.nio.IntBuffer;
import java.util.ArrayList;
import java.util.List;

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

public class Renderer3D {
    private int width;
    private int height;
    private Matrix4f proj;
    private Camera camera;
    private Matrix4f translation;
    private VertexBuffer vertexBuffer;
    private float[] vertexData;
    private int maxTextureSlots;
    private ShaderProgram defaultShaderProgram, currentShaderProgram;
    private ArrayList<String> renderCallNames = new ArrayList<>(50);
    private boolean debug = false;
    private FastTextureLookup textureLookup;

    private static final float FOV = (float) Math.toRadians(60.0f);

    private static final float Z_NEAR = 0.01f;

    private static final float Z_FAR = 1000.f;

    public Renderer3D(int width, int height, boolean msaa) {
        this.width = width;
        this.height = height;

        glEnable(GL_DEPTH_TEST);
        glDepthFunc(GL_NOTEQUAL);


        float aspectRatio = (float) getWidth() / getHeight();
        proj = new Matrix4f().perspective(FOV, aspectRatio,
                Z_NEAR, Z_FAR);
        camera = new Camera();
        translation = new Matrix4f().identity();


        IntBuffer d = BufferUtils.createIntBuffer(1);
        glGetIntegerv(GL_MAX_TEXTURE_IMAGE_UNITS, d);
        maxTextureSlots  = d.get();
        textureLookup = new FastTextureLookup(maxTextureSlots);



        defaultShaderProgram = new ShaderProgram(
                FileReader.readFile(Renderer2D.class.getClassLoader().getResourceAsStream("3DBatchVertexShader.glsl")),
                FileReader.readFile(Renderer2D.class.getClassLoader().getResourceAsStream("3DBatchFragmentShader.glsl"))
        );

        defaultShaderProgram.prepare();
        currentShaderProgram = defaultShaderProgram;
        currentShaderProgram.bind();


        updateCamera2D();

        VertexArray vertexArray = new VertexArray();
        vertexArray.bind();


        vertexArray.setVertexAttributes(
                new VertexAttribute(0, 3, false, "v_pos"),
                new VertexAttribute(1, 2, false, "v_uv"),
                new VertexAttribute(2, 1, false, "v_texindex"),
                new VertexAttribute(3, 4, false, "v_color"),
                new VertexAttribute(4, 1, false, "v_thickness")
        );

        vertexBuffer = new VertexBuffer(1000, vertexArray.getStride());
        vertexArray.build();

        vertexData = new float[vertexBuffer.getNumOfVertices() * vertexBuffer.getVertexDataSize()];
    }

    /***
     * Sets the current shader. This shader must be compiled before calling this method!
     * @param shaderProgram
     */
    public void setShader(ShaderProgram shaderProgram){

        if(currentShaderProgram != shaderProgram) {
            currentShaderProgram = shaderProgram;
            currentShaderProgram.bind();
            updateCamera2D();
        }
    }

    public void updateCamera2D(){
        currentShaderProgram.setMatrix4f("v_model", getTranslation());
        currentShaderProgram.setMatrix4f("v_view", getView());
        currentShaderProgram.setMatrix4f("v_projection", getProj());
        currentShaderProgram.setIntArray("u_textures", createTextureSlots());
    }


    private int[] createTextureSlots() {
        int[] slots = new int[maxTextureSlots];
        for (int i = 0; i < maxTextureSlots; i++) {
            slots[i] = i;
        }
        return slots;
    }
    public VertexBuffer getVertexBuffer() {
        return vertexBuffer;
    }
    public int getWidth() {
        return width;
    }
    public int getHeight() {
        return height;
    }
    public Matrix4f getProj() {
        return proj;
    }
    public Camera getCamera() {
        return camera;
    }
    public Matrix4f getView() {
        return camera.getViewMatrix();
    }
    public Matrix4f getTranslation() {
        return translation;
    }
    private int quadIndex;
    private int nextTextureSlot;
    private Matrix4f transform = new Matrix4f().identity();
    private float originX, originY;
    public Matrix4f getTransform() {
        return transform;
    }
    public void setTransform(Matrix4f transform) {
        this.transform = transform;
    }
    public void resetTransform(){
        setTransform(new Matrix4f().identity());
    }
    public void drawTexture(float x, float y, float w, float h, Texture2D texture){
        drawTexture(x, y, w, h, texture, Color.WHITE);
    }
    public void setOrigin(float x, float y){
        this.originX = x;
        this.originY = y;
    }




    public void drawTexture(float x, float y, float w, float h, Texture2D texture, Color color){
        drawTexture(x, y, w, h, texture, color, new Rect2D(0, 0, 1, 1), false, false);
    }
    public void drawTexture(float x, float y, float w, float h, Texture2D texture, Color color, Rect2D rect2D, boolean xFlip, boolean yFlip) {

        int slot = nextTextureSlot;
        boolean isUniqueTexture = false;



        //Existing texture
        if (textureLookup.hasTexture(texture)) {
            slot = textureLookup.getTexture(texture);
        }

        //Unique Texture
        else {
            glActiveTexture(GL_TEXTURE0 + slot);
            texture.bind();
            texture.setSlot(slot);
            textureLookup.registerTexture(texture, slot);
            isUniqueTexture = true;
        }


        drawQuadGL(x, y, w, h, slot, color, originX, originY, rect2D, -1, xFlip, yFlip);

        if(isUniqueTexture) nextTextureSlot++;

        if(nextTextureSlot == maxTextureSlots)
            render("Next Batch Render [No more rebel.engine.graphics.Texture slots out of " + maxTextureSlots + "]");

    }


    public void drawQuadGL(float x, float y, float w, float h, int slot, Color color, float originX, float originY, Rect2D region, float thickness, boolean xFlip, boolean yFlip){
        //Translate back by origin (for rotation math)
        //This usually takes everything near (0, 0)

        Rect2D copy = new Rect2D(region.x, region.y, region.w, region.h);

        if(xFlip){
            float temp = copy.x;
            copy.x = copy.w;
            copy.w = temp;
        }

        if(yFlip){
            float temp = copy.y;
            copy.y = copy.h;
            copy.h = temp;
        }

        float z = -0.25f;

        Vector4f topLeft = new Vector4f(x - originX, y - originY, z, 1);
        Vector4f topRight = new Vector4f(x + w - originX, y - originY, z, 1);
        Vector4f bottomLeft = new Vector4f(x - originX, y + h - originY, z, 1);
        Vector4f bottomRight = new Vector4f(x + w - originX, y + h - originY, z, 1);

        topLeft.mul(transform);
        topRight.mul(transform);
        bottomLeft.mul(transform);
        bottomRight.mul(transform);



        //Translate forward by origin back to the current position
        topLeft.x += originX;
        topRight.x += originX;
        bottomLeft.x += originX;
        bottomRight.x += originX;

        topLeft.y += originY;
        topRight.y += originY;
        bottomLeft.y += originY;
        bottomRight.y += originY;



        {
            vertexData[(quadIndex * vertexBuffer.getVertexDataSize()) + 0] = topLeft.x;
            vertexData[(quadIndex * vertexBuffer.getVertexDataSize()) + 1] = topLeft.y;
            vertexData[(quadIndex * vertexBuffer.getVertexDataSize()) + 2] = topLeft.z;
            vertexData[(quadIndex * vertexBuffer.getVertexDataSize()) + 3] = copy.x;
            vertexData[(quadIndex * vertexBuffer.getVertexDataSize()) + 4] = copy.y;
            vertexData[(quadIndex * vertexBuffer.getVertexDataSize()) + 5] = slot;
            vertexData[(quadIndex * vertexBuffer.getVertexDataSize()) + 6] = color.r;
            vertexData[(quadIndex * vertexBuffer.getVertexDataSize()) + 7] = color.g;
            vertexData[(quadIndex * vertexBuffer.getVertexDataSize()) + 8] = color.b;
            vertexData[(quadIndex * vertexBuffer.getVertexDataSize()) + 9] = color.a;
            vertexData[(quadIndex * vertexBuffer.getVertexDataSize()) + 10] = thickness;


            vertexData[(quadIndex * vertexBuffer.getVertexDataSize()) + 11] = bottomLeft.x;
            vertexData[(quadIndex * vertexBuffer.getVertexDataSize()) + 12] = bottomLeft.y;
            vertexData[(quadIndex * vertexBuffer.getVertexDataSize()) + 13] = bottomLeft.z;
            vertexData[(quadIndex * vertexBuffer.getVertexDataSize()) + 14] = copy.x;
            vertexData[(quadIndex * vertexBuffer.getVertexDataSize()) + 15] = copy.h;
            vertexData[(quadIndex * vertexBuffer.getVertexDataSize()) + 16] = slot;
            vertexData[(quadIndex * vertexBuffer.getVertexDataSize()) + 17] = color.r;
            vertexData[(quadIndex * vertexBuffer.getVertexDataSize()) + 18] = color.g;
            vertexData[(quadIndex * vertexBuffer.getVertexDataSize()) + 19] = color.b;
            vertexData[(quadIndex * vertexBuffer.getVertexDataSize()) + 20] = color.a;
            vertexData[(quadIndex * vertexBuffer.getVertexDataSize()) + 21] = thickness;


            vertexData[(quadIndex * vertexBuffer.getVertexDataSize()) + 22] = bottomRight.x;
            vertexData[(quadIndex * vertexBuffer.getVertexDataSize()) + 23] = bottomRight.y;
            vertexData[(quadIndex * vertexBuffer.getVertexDataSize()) + 24] = bottomRight.z;
            vertexData[(quadIndex * vertexBuffer.getVertexDataSize()) + 25] = copy.w;
            vertexData[(quadIndex * vertexBuffer.getVertexDataSize()) + 26] = copy.h;
            vertexData[(quadIndex * vertexBuffer.getVertexDataSize()) + 27] = slot;
            vertexData[(quadIndex * vertexBuffer.getVertexDataSize()) + 28] = color.r;
            vertexData[(quadIndex * vertexBuffer.getVertexDataSize()) + 29] = color.g;
            vertexData[(quadIndex * vertexBuffer.getVertexDataSize()) + 30] = color.b;
            vertexData[(quadIndex * vertexBuffer.getVertexDataSize()) + 31] = color.a;
            vertexData[(quadIndex * vertexBuffer.getVertexDataSize()) + 32] = thickness;


            vertexData[(quadIndex * vertexBuffer.getVertexDataSize()) + 33] = topRight.x;
            vertexData[(quadIndex * vertexBuffer.getVertexDataSize()) + 34] = topRight.y;
            vertexData[(quadIndex * vertexBuffer.getVertexDataSize()) + 35] = topRight.z;
            vertexData[(quadIndex * vertexBuffer.getVertexDataSize()) + 36] = copy.w;
            vertexData[(quadIndex * vertexBuffer.getVertexDataSize()) + 37] = copy.y;
            vertexData[(quadIndex * vertexBuffer.getVertexDataSize()) + 38] = slot;
            vertexData[(quadIndex * vertexBuffer.getVertexDataSize()) + 39] = color.r;
            vertexData[(quadIndex * vertexBuffer.getVertexDataSize()) + 40] = color.g;
            vertexData[(quadIndex * vertexBuffer.getVertexDataSize()) + 41] = color.b;
            vertexData[(quadIndex * vertexBuffer.getVertexDataSize()) + 42] = color.a;
            vertexData[(quadIndex * vertexBuffer.getVertexDataSize()) + 43] = thickness;


        }
        quadIndex++;


        if(quadIndex == vertexBuffer.maxQuads()) render("Next Batch Render");
    }
    public void render(){
        render("Final Draw Call [rebel.engine.graphics.Renderer2D.render()]");

        if(debug){
            System.out.println("Renderer2D (" + this + ") - Debug");

            for(String call : getRenderCalls()){
                System.out.print("\t" + call + "\n");
            }
            System.out.println("\n");
        }


        renderCallNames.clear();
    }

    public void render(String renderName) {
        renderCallNames.add(renderName);

        glBindBuffer(GL_ARRAY_BUFFER, getVertexBuffer().myVbo);



        glBufferSubData(GL_ARRAY_BUFFER, 0, vertexData);


        int numOfIndices = quadIndex * 6;
        int[] indices = new int[numOfIndices];
        int offset = 0;

        for (int j = 0; j < numOfIndices; j += 6) {

            indices[j] =         offset;
            indices[j + 1] = 1 + offset;
            indices[j + 2] = 2 + offset;
            indices[j + 3] = 2 + offset;
            indices[j + 4] = 3 + offset;
            indices[j + 5] =     offset;

            offset += 4;
        }

        glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, getVertexBuffer().myEbo);
        glBufferSubData(GL_ELEMENT_ARRAY_BUFFER, 0, indices);
        glDrawElements(GL_TRIANGLES, indices.length, GL_UNSIGNED_INT, 0);


        vertexData = new float[vertexBuffer.getNumOfVertices() * vertexBuffer.getVertexDataSize()];
        quadIndex = 0;
        nextTextureSlot = 0;
        textureLookup.clear();

    }
    public List<String> getRenderCalls(){
        return new ArrayList<>(renderCallNames);
    }


    public void clear(Color color) {
        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
        glClearColor(color.r, color.g, color.b, color.a);
    }


}

This is my Vertex Shader:

#version 330 core
layout (location = 0) in vec3 v_pos;
layout (location = 1) in vec2 v_uv;
layout (location = 2) in float v_texindex;
layout (location = 3) in vec4 v_color;
layout (location = 4) in float v_thickness;




out vec2 f_uv;
out float f_texindex;
out vec4 f_color;
out vec2 f_origin;
out vec2 f_size;
out float f_thickness;

uniform mat4 v_model;
uniform mat4 v_view;
uniform mat4 v_projection;






void main() {

    f_texindex = v_texindex;
    f_uv = v_uv;
    f_color = v_color;
    f_thickness = v_thickness;


    gl_Position = v_projection * v_view * v_model * vec4(v_pos, 1.0);
}

And my Fragment Shader

#version 330 core
out vec4 FragColor;

in vec2 f_uv;

uniform sampler2D u_textures[32];
in float f_texindex;
in vec4 f_color;
in float f_thickness;
layout(origin_upper_left) in vec4 gl_FragCoord;


void main()
{


    int index = int(f_texindex);



    if(index == -1){
        FragColor = f_color;
    }
    else if(index == -2){
        vec2 uv = f_uv * 2.0 - 1.0;

        float distance = length(uv);

        if(distance <= 1.0 && distance >= (1.0 - f_thickness))
        FragColor = f_color;
        else
        discard;

    }
    else {


        if(index == 0) FragColor += texture(u_textures[0], f_uv) * f_color;
        if(index == 1) FragColor += texture(u_textures[1], f_uv) * f_color;
        if(index == 2) FragColor += texture(u_textures[2], f_uv) * f_color;
        if(index == 3) FragColor += texture(u_textures[3], f_uv) * f_color;
        if(index == 4) FragColor += texture(u_textures[4], f_uv) * f_color;
        if(index == 5) FragColor += texture(u_textures[5], f_uv) * f_color;
        if(index == 6) FragColor += texture(u_textures[6], f_uv) * f_color;
        if(index == 7) FragColor += texture(u_textures[7], f_uv) * f_color;
        if(index == 8) FragColor += texture(u_textures[8], f_uv) * f_color;
        if(index == 9) FragColor += texture(u_textures[9], f_uv) * f_color;
        if(index == 10) FragColor += texture(u_textures[10], f_uv) * f_color;
        if(index == 11) FragColor += texture(u_textures[11], f_uv) * f_color;
        if(index == 12) FragColor += texture(u_textures[12], f_uv) * f_color;
        if(index == 13) FragColor += texture(u_textures[13], f_uv) * f_color;
        if(index == 14) FragColor += texture(u_textures[14], f_uv) * f_color;
        if(index == 15) FragColor += texture(u_textures[15], f_uv) * f_color;
        if(index == 16) FragColor += texture(u_textures[16], f_uv) * f_color;
        if(index == 17) FragColor += texture(u_textures[17], f_uv) * f_color;
        if(index == 18) FragColor += texture(u_textures[18], f_uv) * f_color;
        if(index == 19) FragColor += texture(u_textures[19], f_uv) * f_color;
        if(index == 20) FragColor += texture(u_textures[20], f_uv) * f_color;
        if(index == 21) FragColor += texture(u_textures[21], f_uv) * f_color;
        if(index == 22) FragColor += texture(u_textures[22], f_uv) * f_color;
        if(index == 23) FragColor += texture(u_textures[23], f_uv) * f_color;
        if(index == 24) FragColor += texture(u_textures[24], f_uv) * f_color;
        if(index == 25) FragColor += texture(u_textures[25], f_uv) * f_color;
        if(index == 26) FragColor += texture(u_textures[26], f_uv) * f_color;
        if(index == 27) FragColor += texture(u_textures[27], f_uv) * f_color;
        if(index == 28) FragColor += texture(u_textures[28], f_uv) * f_color;
        if(index == 29) FragColor += texture(u_textures[29], f_uv) * f_color;
        if(index == 30) FragColor += texture(u_textures[30], f_uv) * f_color;
        if(index == 31) FragColor += texture(u_textures[31], f_uv) * f_color;
    }

}

I was expecting it to render the textures without any glitchy artifacts, mostly because I carried over the Texture batching logic from my 2D Renderer. I'm sure I'm missing something simple. I'm using an NVIDIA GeForce MX450 PCIe/SSE2.

glGetError() reported no errors, and the LWJGL OpenGL Callback was silent the whole time. I've verified using RenderDoc that the textures are bound to the correct slots.

Upvotes: 0

Views: 92

Answers (2)

sRGB
sRGB

Reputation: 1

I just fixed an issue similar to this with my shaders. The code worked perfectly fine on Intel GPUs but when I would run it on an NVIDIA GPU (NVIDIA GeForce GTX 970m/PCIe/SSE2 and NVIDIA GeForce RTX 4060/PCIe/SSE2) I would get glitching similar to what you posted.

It would appear that the NVIDIA drivers are interpolating the texture index across the fragments, causing different textures to be selected at different fragments. I will use my own shaders for an example.

My old Vertex Shader:

#version 330 core
layout (location = 0) in vec3 i_Loc;
layout (location = 1) in vec2 i_TexCoords;
layout (location = 2) in float i_TexID;

out vec2 o_texCoords;
out float o_texID;

uniform mat4 transform;
uniform mat4 view;
uniform mat4 projection;

void main() {
    gl_Position = projection * view * transform * vec4(i_Loc, 1.0);
    o_texCoords = i_TexCoords;
    o_texID = i_TexID;
}

My updated (and fixed) Vertex shader

#version 330 core
layout (location = 0) in vec3 i_Loc;
layout (location = 1) in vec2 i_TexCoords;
layout (location = 2) in float i_TexID;

out vec2 o_texCoords;
flat out int o_texID;

uniform mat4 transform;
uniform mat4 view;
uniform mat4 projection;

void main() {
    gl_Position = projection * view * transform * vec4(i_Loc, 1.0);
    o_texCoords = i_TexCoords;

    // Required since NVIDIA drivers try to interpolate the texID.
    o_texID = int(floor(i_TexID));
}

The flat keyword indicates to the compiler not to interpolate the value. Hopefully this helps you fix your issue! I spent a couple of frustrating months on this problem (with my only solution prior being switching GPUs).

Upvotes: 0

Austin Romney
Austin Romney

Reputation: 1

Although the problem may be within your texture loading, I think it is more likely within your fragment shader. I think that stating FragColor as FragColor += texture(u_textures[_], f_uv) * f_color opens up your program to many different issues. I understand that uniform arrays must be initialized in GLSL. When batching, there is another solution. Shader Storage Buffer Objects (SSBOs). I would recommend reading this

Upvotes: 0

Related Questions