Search code examples
javaopengltextureslwjgl

OpenGL is only rendering one pixel of a texture on a quad?


I wanted a quad with a texture on it with LWJGL from scratch but for some reason, only the first top right texel/pixel is applied on the quad.

I also tried this with multiple textures and still can't solve the issue.

Here is the texture: Texture I am using

Here is what I get: The Output

package com.codebitcookie.engine;

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

import com.codebitcookie.graphics.Color;
import com.codebitcookie.graphics.Mesh;
import com.codebitcookie.graphics.Texture;
import com.codebitcookie.maths.Matrix4f;
import com.codebitcookie.shaders.Shader;

import static org.lwjgl.glfw.GLFW.*;

public class Core {
    
    private static float[] verts = {
//      Quad
//      -0.5f,  0.5f, 0.5f,
//       0.5f,  0.5f, 0.5f,
//       0.5f, -0.5f, 0.5f,
//      
//      -0.5f,  0.5f, 0.5f,
//       0.5f, -0.5f, 0.5f,
//      -0.5f, -0.5f, 0.5f
            
//      Triangle        
//       0.0f,  0.5f, 0.5f,
//       0.5f, -0.5f, 0.5f,
//      -0.5f, -0.5f, 0.5f
            
//      You might have to flip the Y-values because of the way OpenGL's coordinates work 
//      (i.e. (0,0) is normally bottom-left instead of top-left)
            
//      Quad
         0, 1,
         1, 1,
         1, 0,
         1, 0,
         0, 0,
         0, 1
            
//      Triangle
//          0, 1,
//       0.5f, 0,
//          1, 1
    };

//  int[] indices = {
//      0, 1, 3,
//      3, 1, 2
//  };
    
    private static float[] uvs = {
//      0, 0,
//      0, 1,
//      1, 1,
//      1, 0
            
         0, 1,
         1, 1,
         1, 0,
         1, 0,
         0, 0,
         0, 1
    };
    
    public static void main(String[] args) {
        long window = Application.init();
        Color ubuntu = Color.ubuntu();
        
        //1920 x 1080 is a 16:9 aspect ratio thats why we take the aspect ratio of 1080p and apply it on a 10x10x10 projection
//      Matrix4f ortho = Matrix4f.orthographic(-10.0f, 10.0f, -10.0f * 9.0f / 16.0f, 10.0f * 9.0f / 16.0f, 1.0f, -1.0f);
        Matrix4f ortho = Matrix4f.orthographic(0.0f, Application.getWidth(), Application.getHeight(), 0.0f, 1.0f, -1.0f); //TODO: CHECK IF Z WORKS
        
        Mesh mesh = new Mesh(verts, uvs);
        Shader shader = new Shader("res/shaders/VertexShader.vs", "res/shaders/FragmentShader.fs");
        Texture texture = new Texture("minecraftTextureAtlas", "res/textures/minecraftTextureAtlas.png");
//      Texture texture = new Texture("minecraftPlanks", "res/textures/minecraftPlanks.jpeg");
        
        GL11.glClearColor(ubuntu.getR(), ubuntu.getG(), ubuntu.getB(), ubuntu.getA()); //set a color for when you call glClear to clear the screen
        
        shader.bind();
        shader.setUniformColor("matColor", Color.white());
        shader.setUniformMat4f("projection", ortho);
        shader.unbind();
        
        while(!glfwWindowShouldClose(window)) {
            glfwPollEvents(); //processes events that are in the event queue and then returns immediately.
            
            //this clears the color buffer and sets it to what we set it to when we call glClearColor()
            //GL_COLOR_BUFFER_BIT: contains the RGB info for every pixel.
            //GL_DEPTH_BUFFER_BIT: contains the distance between pixels from screen or for making layers in our case for e.g some pixel is in front of others
            //GL_STENCIL_BUFFER_BIT: A custom buffer for your use for per pixel info
            //And many others
            GL11.glClear(GL11.GL_COLOR_BUFFER_BIT | GL11.GL_DEPTH_BUFFER_BIT);

            shader.bind();
            texture.bind();
            mesh.render();
            texture.unbind();
            shader.unbind();
            
            glfwSwapBuffers(window); //Swap Buffers
        }
        
        Texture.cleanUp();
        shader.cleanUp();
        mesh.cleanUp();
        glfwTerminate(); // Destroys all windows and cursors, frees and restores resources. you must call glfwInit after.
    }

}

******************** Application.java ********************

package com.codebitcookie.engine;

import static org.lwjgl.glfw.GLFW.*;
import static org.lwjgl.system.MemoryUtil.NULL;

import org.lwjgl.glfw.GLFWVidMode;
import org.lwjgl.opengl.GL;
import org.lwjgl.opengl.GL11;

import com.codebitcookie.graphics.Color;

public class Application {
    
    private static final String TITLE = "2D Game Engine /w GUI";
    private static int width = 1280;
    private static int height = 720;
    private static long window;
    
    public static long init() {
        //check if GLFW is initialized if not we throw an exception
        if(!glfwInit()) {
            throw new IllegalStateException("GLFW failed to be initialized");
        }
        
//      GLFW.glfwDefaultWindowHints(); //equals to the two lines below
        glfwWindowHint(GLFW_VISIBLE, GL11.GL_FALSE); //makes the window visible
        glfwWindowHint(GLFW_RESIZABLE, GL11.GL_TRUE); //makes the window resizable
        
        //sets OpenGL to 3.3; major is 3.*** and minor is ***.3 = 3.3
        glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
        glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
        
        //for more info on these, check out: 
        //https://community.khronos.org/t/forward-compatible-vs-core-profile/65039
        //https://www.khronos.org/opengl/wiki/OpenGL_Context
        glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL11.GL_TRUE); //makes mac run nicely but restores all deprecated functionality
    
        window = glfwCreateWindow(width, height, TITLE, NULL, NULL);
        
        //if window could not be created
        if(window == NULL) {
            glfwTerminate(); // Destroys all windows and cursors, frees and restores resources. you must call glfwInit after.
            throw new RuntimeException("Unable to create window " + window);
        }
        
        GLFWVidMode vidmode = glfwGetVideoMode(glfwGetPrimaryMonitor());
        glfwSetWindowPos(window, (vidmode.width() - width) / 2, (vidmode.height() - height) / 2);

        glfwMakeContextCurrent(window);
        glfwShowWindow(window); //show window which we hid in the window hints
        
        GL.createCapabilities(); 
        
        //I don't have screen tearing issues so I am commenting this out
//        glfwSwapInterval(1); 
    
        GL11.glEnable(GL11.GL_DEPTH_TEST); 
        
        return window;
    }

    public static String getTitle() {
        return TITLE;
    }

    public static int getWidth() {
        return width;
    }

******************** Mesh.java ********************

package com.codebitcookie.graphics;

import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL15;
import org.lwjgl.opengl.GL20;
import org.lwjgl.opengl.GL30;

import com.codebitcookie.utils.BufferUtils;

public class Mesh {

    public final static int VERTEX_ATTRIB = 0;
    public final static int TEXCOORD_ATTRIB = 1;
    
    private int vao, vbo, tcbo; //Vertex Array Obj, Vertex Buffer Obj, Texture Coords Buffer Obj
    private float[] vertices, textureCoordinates;
    
    public Mesh(float[] vertices, float[] textureCoordinates) {
        this.vertices = vertices;
        this.textureCoordinates = textureCoordinates;
    
        //Create and bind VAO
        vao = GL30.glGenVertexArrays();
        GL30.glBindVertexArray(vao);
        
        //Create and bind VBO, creates a buffer data store and stores that in VAO
        vbo = GL15.glGenBuffers();
        GL15.glBindBuffer(GL15.GL_ARRAY_BUFFER, vbo);
        
        GL15.glBufferData(GL15.GL_ARRAY_BUFFER, BufferUtils.createFloatBuffer(vertices), GL15.GL_STATIC_DRAW);
        GL20.glVertexAttribPointer(VERTEX_ATTRIB, 2, GL11.GL_FLOAT, false, 0, 0);
        
        //Create and bind TCBO, creates a buffer data store and stores that in VAO
        tcbo = GL15.glGenBuffers();
        GL15.glBindBuffer(GL15.GL_ARRAY_BUFFER, tcbo);
        
        GL15.glBufferData(GL15.GL_ARRAY_BUFFER, BufferUtils.createFloatBuffer(textureCoordinates), GL15.GL_STATIC_DRAW);
        GL20.glVertexAttribPointer(TEXCOORD_ATTRIB, 2, GL11.GL_UNSIGNED_INT, false, 0, 0);
        
        GL30.glBindVertexArray(0); //unbind latest bounded VAO
    }

    public void render() {
        GL30.glBindVertexArray(vao);
        
        GL20.glEnableVertexAttribArray(1);
        GL20.glEnableVertexAttribArray(0); //enabling the 0th attribute list index (ORDER OF ENABLING / DISABLING DOES'NT MATTER)

        GL11.glDrawArrays(GL11.GL_TRIANGLES, 0, vertices.length);
        
        GL20.glDisableVertexAttribArray(0); //disabling the 0th attribute list index 
        GL20.glDisableVertexAttribArray(1);
        
        GL30.glBindVertexArray(0);
    }
    
    //delete the VAOs and the VBOs
    public void cleanUp() {
        GL30.glDeleteVertexArrays(vao);
        GL15.glDeleteBuffers(vbo);
        GL15.glDeleteBuffers(tcbo);
    }
    
}

******************** Shader.java ********************

package com.codebitcookie.shaders;

import java.util.HashMap;
import java.util.Map;

import javax.swing.plaf.PanelUI;

import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL20;
import org.lwjgl.opengl.GL30;
import org.lwjgl.system.CallbackI.P;

import com.codebitcookie.graphics.Color;
import com.codebitcookie.maths.Matrix4f;
import com.codebitcookie.maths.Vector3f;
import com.codebitcookie.utils.BufferUtils;
import com.codebitcookie.utils.FileUtils;

public class Shader {
    
    private Map<String, Integer> locationCache = new HashMap<>();
    
    private static int programID;
    private int vertexShaderID;
    private int fragmentShaderID;
    
    private boolean enabled = false;
    
    public Shader(String vertexShaderPath, String fragmentShaderPath) {
        
        //Create Shaders
        programID = GL20.glCreateProgram();
        vertexShaderID = GL20.glCreateShader(GL20.GL_VERTEX_SHADER);
        fragmentShaderID = GL20.glCreateShader(GL20.GL_FRAGMENT_SHADER);
        
        //After the shader is created we assign the actual shader file to the shader
        GL20.glShaderSource(vertexShaderID, FileUtils.loadAsString(vertexShaderPath));
        GL20.glShaderSource(fragmentShaderID, FileUtils.loadAsString(fragmentShaderPath));
        
        //Compile the Shaders, and check for any compilation errors
        GL20.glCompileShader(vertexShaderID);
        if(GL20.glGetShaderi(vertexShaderID, GL20.GL_COMPILE_STATUS) == GL11.GL_FALSE) {
            System.err.println("[VERTEX] Compile Error: " + GL20.glGetShaderInfoLog(vertexShaderID, 2048));
            System.exit(-1);
        }
        
        GL20.glCompileShader(fragmentShaderID);
        if(GL20.glGetShaderi(fragmentShaderID, GL20.GL_COMPILE_STATUS) == GL11.GL_FALSE) {
            System.err.println("[FRAGMENT] Compile Error: " + GL20.glGetShaderInfoLog(fragmentShaderID, 2048));
            System.exit(-1);
        }
        
        //Attach shaders to program
        GL20.glAttachShader(programID, vertexShaderID);
        GL20.glAttachShader(programID, fragmentShaderID);
        
        //set layout location explicitly in java
        GL20.glBindAttribLocation(programID, 0, "vertices");
        GL20.glBindAttribLocation(programID, 1, "uv");
            
        //Links together and creates executables for all shaders that were attached to the program, and check for any linking errors
        GL20.glLinkProgram(programID); 
        if(GL20.glGetProgrami(programID, GL20.GL_LINK_STATUS) == GL11.GL_FALSE) {
            System.err.println("[PROGRAM] Link Error: " + GL20.glGetProgramInfoLog(programID, 2048));
            System.exit(-1);
        }
        
        //Checks if the executables created are valid and can execute given the current OpenGL state, and check for any validation errors
        GL20.glValidateProgram(programID);
        
        if(GL20.glGetProgrami(programID, GL20.GL_VALIDATE_STATUS) == GL11.GL_FALSE) {
            System.err.println("[PROGRAM] Validation Error: " + GL20.glGetProgramInfoLog(programID, 2048));
            System.exit(-1);
        }
                
    }
    
    public void bind() {
        GL20.glUseProgram(programID);
        enabled = true;
    }
    public void unbind() {
        GL20.glUseProgram(0);
    }
    
    public void cleanUp() {
        unbind();
        locationCache.clear();
        GL20.glDetachShader(programID, vertexShaderID);
        GL20.glDetachShader(programID, fragmentShaderID);
        GL20.glDeleteShader(vertexShaderID);
        GL20.glDeleteShader(fragmentShaderID);
        GL20.glDeleteProgram(programID);
    }
    
    public int getUniformLocation(String name) {
        if(locationCache.containsKey(name)) {
            return locationCache.get(name);
        }
        
        int location = GL20.glGetUniformLocation(programID, name);
        
        if(location == -1) {
            System.err.println("[Shader] Error: Couldn't get the location uniform variable " + name);
        } else {
            locationCache.put(name, location);
        }
        
        return location;
    }
    
    public void setUniformColor(String name, Color c) {
        if(!enabled) bind();
        GL20.glUniform4f(getUniformLocation(name), c.getR(), c.getG(), c.getB(), c.getA());
    }
    public void setUniform1i(String name, int value) {
        if(!enabled) bind();
        GL20.glUniform1i(getUniformLocation(name), value);
    }
    public void setUniform1b(String name, boolean value) {
        if(!enabled) bind();
        
        int toLoad = value ? 1 : 0;
        GL20.glUniform1f(getUniformLocation(name), toLoad);
    }
    public void setUniform1f(String name, float value) {
        if(!enabled) bind();
        GL20.glUniform1f(getUniformLocation(name), value);
    }
    public void setUniform2f(String name, float x, float y) {
        if(!enabled) bind();
        GL20.glUniform2f(getUniformLocation(name), x, y);
    }
    public void setUniform3f(String name, Vector3f value) {
        if(!enabled) bind();
        GL20.glUniform3f(getUniformLocation(name), value.getX(), value.getY(), value.getZ());
    }   
    public void setUniformMat4f(String name, Matrix4f matrix) {
        if(!enabled) bind();
        GL20.glUniformMatrix4fv(getUniformLocation(name), false, BufferUtils.createFloatBuffer(matrix.getMatrix())); //false is for transpose, if we didn't setup our matrices in column major we could say true which would to this for us automatically but extra work for the memory, I think?
    }
}

******************** VertexShader.vs ********************

//It doesn't matter what extension or name you give these Shader files as we only read the text
//Vertex files are used for positioning vertices and that's it.
//Will be run per vertex, so 3 times

//Uniform is the type of variable which can communicate with java code / not glsl 

//Uses version 3.3 with the core profile

#version 330 core

in vec2 position;
in vec2 textureCoords;

out vec4 color;
out vec2 uvCoords;

uniform vec4 matColor;
uniform mat4 projection;

void main(){

    //our triangle, or whatever is drawn is a single unit, and because of our special projection matrix is rendered as one single pixel
    //to fix this problem we create a world position (see notes) which scales the unit up (position * 100) 
    //and moves it by 100 pixels from the top and 100 pixels from the left ( + vec2(100, 100) )
    
    vec2 worldPosition = (position * 100);// + vec2(100, 100);
    gl_Position = projection * vec4(worldPosition, 0.0f, 1.0f);
    
    color = matColor;
    uvCoords = textureCoords;
}

******************* FragmentShader.fs ********************

#version 330 core

uniform sampler2D sampler;


in vec4 color;
in vec2 uvCoords;

out vec4 out_color;
void main() {
    out_color = color * texture(sampler, uvCoords);
}

******************** Texture.java ********************

package com.codebitcookie.graphics;

import java.nio.ByteBuffer;
import java.nio.IntBuffer;
import java.util.LinkedList;
import java.util.List;

import org.lwjgl.BufferUtils;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL13;
import org.lwjgl.stb.STBImage;

public class Texture {
    
    private String name = "";
    private String filepath = "";
    
    private int id;
    private int width, height;

    private static List<Texture> textureInstances = new LinkedList<>();
    private static Texture tmp = null;
    private static int i = 0;
    
    public Texture(String name, String filepath) {
        this.name = name;
        this.filepath = filepath;
        
        IntBuffer width = BufferUtils.createIntBuffer(1);
        IntBuffer height = BufferUtils.createIntBuffer(1);
        IntBuffer channels = BufferUtils.createIntBuffer(1);
        
        //get pixel data in RGBA format with STBImage, if we used BufferedImage we would get ARGB and we would have to change it to RGBA
        ByteBuffer data = STBImage.stbi_load(filepath, width, height, channels, 4);
        
        id = GL11.glGenTextures();
        this.width = width.get();
        this.height = height.get();
        
        GL11.glBindTexture(GL11.GL_TEXTURE_2D, id);
        
        //see notes for this
        //We do GL_NEAREST because in our game we are using pixel art and it won't look sharp if we use bi linear filtering
        GL11.glTexParameterf(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MIN_FILTER, GL11.GL_NEAREST);
        GL11.glTexParameterf(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MAG_FILTER, GL11.GL_NEAREST);
        
        GL11.glTexImage2D(GL11.GL_TEXTURE_2D, 0, GL11.GL_RGBA, this.width, this.height, 0, GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE, data);
        STBImage.stbi_image_free(data);
        
        textureInstances.add(this);
    }
    
    public static Texture find(String texturename) {
        for(i = 0; i < textureInstances.size(); i++) {
            tmp = getTextureInstances().get(i);
            if(tmp.name.startsWith(texturename))
                return tmp;
        }
        
        return null;
    }
    
    public void bind() {
        GL13.glActiveTexture(GL13.GL_TEXTURE0);
        GL11.glBindTexture(GL11.GL_TEXTURE_2D, id);
    }
    
    public void unbind() {
        GL11.glBindTexture(GL11.GL_TEXTURE_2D, 0);
    }
    
    public static void cleanUp() {
        for(i = 0; i < textureInstances.size(); i++) {
            GL13.glDeleteTextures(textureInstances.get(i).getId());
        }
    }
    
    public String getName() {
        return name;
    }
    public int getId() {
        return id;
    }
    public int getWidth() {
        return width;
    }
    public int getHeight() {
        return height;
    }
    public static List<Texture> getTextureInstances() {
        return textureInstances;
    }
    
}

Solution

  • Originally this guy answered my post https://stackoverflow.com/users/2579738/bdl but on a comment, I asked him to post it as an answer, but he didn't. Unfortunately this resulted with my post getting deleted. That is why I decided to post it as an answer. The answer is:

    GL20.glVertexAttribPointer(TEXCOORD_ATTRIB, 2, GL11.GL_UNSIGNED_INT, You are using float data but tell OpenGL that it contains unsigned integers.