I'm new to OpenGL, and am trying to draw two squares with different textures. I'm using lwjgl 3 as the interface to OpenGL, but I believe the OpenGL calls should look familiar for people who use OpenGL in other languages. My main loop looks like this:
while (glfwWindowShouldClose(windowId) == GLFW_FALSE) {
glClear(GL_COLOR_BUFFER_BIT);
glUseProgram(shaderProgramId);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ebo);
// DRAW TEXTURE 1
specifyVertexAttributes(shaderProgramId);
glBindTexture(GL_TEXTURE_2D, texture1.getId());
glBindBuffer(GL_ARRAY_BUFFER, vbo1);
glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);
// DRAW TEXTURE 2
specifyVertexAttributes(shaderProgramId);
glBindTexture(GL_TEXTURE_2D, texture2.getId());
glBindBuffer(GL_ARRAY_BUFFER, vbo2);
glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);
glfwSwapBuffers(windowId);
glfwPollEvents();
}
When I comment out the code that draws texture 2, texture 1 draws in the correct place:
When I comment out the code that draws texture 1, texture 2 draws in the correct place:
But when I try to draw both of the textures, they switch places:
I realize the code snippet above is probably not sufficient to diagnose this issue. I've created a standalone java class that contains all the OpenGL calls that I'm making in order to draw these textures: StandaloneMultiTextureExample. The repo which contains that file also builds with gradle. It should be really easy for anyone who is willing to help to check out the repo and run that example class.
Edit: Copy of StandaloneMultiTextureExample.java (without imports)
public class StandaloneMultiTextureExample {
private final GLFWErrorCallback errorCallback = new LoggingErrorCallback();
private final GLFWKeyCallback keyCallback = new ApplicationClosingKeyCallback();
public void run() {
if ( glfwInit() != GLFW_TRUE ) {
throw new IllegalStateException("Unable to initialize GLFW");
}
glfwSetErrorCallback(errorCallback);
int width = 225;
int height = 200;
long windowId = createWindow(width, height);
glfwSetKeyCallback(windowId, keyCallback);
glfwShowWindow(windowId);
GL.createCapabilities();
Texture texture1 = createTexture("multiTextureExample/texture1.png");
Texture texture2 = createTexture("multiTextureExample/texture2.png");
int shaderProgramId = createShaderProgram(
"multiTextureExample/textureShader.vert",
"multiTextureExample/textureShader.frag");
IntBuffer elements = BufferUtils.createIntBuffer(2 * 3);
elements.put(0).put(1).put(2);
elements.put(2).put(3).put(0);
elements.flip();
int ebo = glGenBuffers();
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ebo);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, elements, GL_STATIC_DRAW);
float x1 = 0;
float y1 = 0;
float x2 = x1 + texture1.getWidth();
float y2 = y1 + texture1.getHeight();
float x3 = 25;
float x4 = x3 + texture2.getWidth();
FloatBuffer texture1Vertices = BufferUtils.createFloatBuffer(4 * 7);
texture1Vertices.put(x1).put(y1).put(1).put(1).put(1).put(0).put(0);
texture1Vertices.put(x2).put(y1).put(1).put(1).put(1).put(1).put(0);
texture1Vertices.put(x2).put(y2).put(1).put(1).put(1).put(1).put(1);
texture1Vertices.put(x1).put(y2).put(1).put(1).put(1).put(0).put(1);
texture1Vertices.flip();
FloatBuffer texture2Vertices = BufferUtils.createFloatBuffer(4 * 7);
texture2Vertices.put(x3).put(y1).put(1).put(1).put(1).put(0).put(0);
texture2Vertices.put(x4).put(y1).put(1).put(1).put(1).put(1).put(0);
texture2Vertices.put(x4).put(y2).put(1).put(1).put(1).put(1).put(1);
texture2Vertices.put(x3).put(y2).put(1).put(1).put(1).put(0).put(1);
texture2Vertices.flip();
int vbo1 = glGenBuffers();
glBindBuffer(GL_ARRAY_BUFFER, vbo1);
glBufferData(GL_ARRAY_BUFFER, texture1Vertices, GL_STATIC_DRAW);
int vbo2 = glGenBuffers();
glBindBuffer(GL_ARRAY_BUFFER, vbo2);
glBufferData(GL_ARRAY_BUFFER, texture2Vertices, GL_STATIC_DRAW);
specifyUniformVariables(windowId, shaderProgramId);
glClearColor(0.5f, 0.5f, 0.5f, 1.0f);
while (glfwWindowShouldClose(windowId) == GLFW_FALSE) {
glClear(GL_COLOR_BUFFER_BIT);
glUseProgram(shaderProgramId);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ebo);
specifyVertexAttributes(shaderProgramId);
glBindTexture(GL_TEXTURE_2D, texture1.getId());
glBindBuffer(GL_ARRAY_BUFFER, vbo1);
glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);
specifyVertexAttributes(shaderProgramId);
glBindTexture(GL_TEXTURE_2D, texture2.getId());
glBindBuffer(GL_ARRAY_BUFFER, vbo2);
glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);
glfwSwapBuffers(windowId);
glfwPollEvents();
}
}
private void specifyUniformVariables(long windowId, int shaderProgramId) {
int uniModel = getUniform(shaderProgramId, "model");
FloatBuffer model = BufferUtils.createFloatBuffer(16);
new Matrix4f().get(model);
glUniformMatrix4fv(uniModel, false, model);
FloatBuffer view = BufferUtils.createFloatBuffer(16);
new Matrix4f().get(view);
int uniView = getUniform(shaderProgramId, "view");
glUniformMatrix4fv(uniView, false, view);
WindowSize windowSize = getWindowSize(windowId);
int uniProjection = getUniform(shaderProgramId, "projection");
FloatBuffer projection = BufferUtils.createFloatBuffer(16);
new Matrix4f().ortho2D(0, windowSize.getWidth(), 0, windowSize.getHeight()).get(projection);
glUniformMatrix4fv(uniProjection, false, projection);
}
private void specifyVertexAttributes(int shaderProgramId) {
int stride = 7 * Float.BYTES;
int posAttrib = getAttribute(shaderProgramId, "position");
glEnableVertexAttribArray(posAttrib);
glVertexAttribPointer(posAttrib, 2, GL_FLOAT, false, stride, 0);
int colAttrib = getAttribute(shaderProgramId, "color");
glEnableVertexAttribArray(colAttrib);
glVertexAttribPointer(colAttrib, 3, GL_FLOAT, false, stride, 2 * Float.BYTES);
int texAttrib = getAttribute(shaderProgramId, "texcoord");
glEnableVertexAttribArray(texAttrib);
glVertexAttribPointer(texAttrib, 2, GL_FLOAT, false, stride, 5 * Float.BYTES);
}
public long createWindow(int width, int height) {
glfwDefaultWindowHints();
glfwWindowHint(GLFW_VISIBLE, GLFW_FALSE);
glfwWindowHint(GLFW_RESIZABLE, GLFW_TRUE);
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 0);
long windowId = glfwCreateWindow(width, height, "Hello World!", NULL, NULL);
if (windowId == NULL) {
throw new RuntimeException("Failed to create the GLFW window");
}
// Get the resolution of the primary monitor
GLFWVidMode vidmode = glfwGetVideoMode(glfwGetPrimaryMonitor());
// Center our window
glfwSetWindowPos(
windowId,
(vidmode.width() - width) / 2,
(vidmode.height() - height) / 2
);
// Make the OpenGL context current
glfwMakeContextCurrent(windowId);
// Enable v-sync
glfwSwapInterval(1);
return windowId;
}
public WindowSize getWindowSize(long windowId) {
IntBuffer width = BufferUtils.createIntBuffer(1);
IntBuffer height = BufferUtils.createIntBuffer(1);
GLFW.glfwGetFramebufferSize(windowId, width, height);
return new WindowSize(width.get(), height.get());
}
public Texture createTexture(String textureResource) {
int textureId = glGenTextures();
glBindTexture(GL_TEXTURE_2D, textureId);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_BORDER);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_BORDER);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
ARGBImage image = new ImageService().loadClasspathImage(textureResource);
glTexImage2D(
GL_TEXTURE_2D,
0,
GL_RGBA8,
image.getWidth(),
image.getHeight(),
0,
GL_RGBA,
GL_UNSIGNED_BYTE,
image.getContents());
return new Texture(textureId, image.getWidth(), image.getHeight());
}
public int createShaderProgram(String vertexResource, String fragmentResource) {
int vertexShader = glCreateShader(GL_VERTEX_SHADER);
String vertexSource = getClasspathResource(vertexResource);
glShaderSource(vertexShader, vertexSource);
glCompileShader(vertexShader);
validateShaderCompilation(vertexShader);
int fragmentShader = glCreateShader(GL_FRAGMENT_SHADER);
String fragmentSource = getClasspathResource(fragmentResource);
glShaderSource(fragmentShader, fragmentSource);
glCompileShader(fragmentShader);
validateShaderCompilation(fragmentShader);
int shaderProgramId = glCreateProgram();
glAttachShader(shaderProgramId, vertexShader);
glAttachShader(shaderProgramId, fragmentShader);
glLinkProgram(shaderProgramId);
validateShaderProgram(shaderProgramId);
glUseProgram(shaderProgramId);
return shaderProgramId;
}
private static String getClasspathResource(String resourceName) {
URL url = Resources.getResource(resourceName);
try {
return Resources.toString(url, Charsets.UTF_8);
} catch (IOException e) {
throw Throwables.propagate(e);
}
}
private static void validateShaderCompilation(int shader) {
int status = glGetShaderi(shader, GL_COMPILE_STATUS);
if (status != GL_TRUE) {
throw new RuntimeException(glGetShaderInfoLog(shader));
}
}
private static void validateShaderProgram(int shaderProgram) {
int status = glGetProgrami(shaderProgram, GL_LINK_STATUS);
if (status != GL_TRUE) {
throw new RuntimeException(glGetProgramInfoLog(shaderProgram));
}
}
public int getUniform(int shaderProgramId, String uniformName) {
int location = glGetUniformLocation(shaderProgramId, uniformName);
if (location == -1) {
throw new IllegalArgumentException("Could not find uniform: "
+ uniformName + " for shaderProgramId: " + shaderProgramId);
} else {
return location;
}
}
public int getAttribute(int shaderProgramId, String attribute) {
int location = glGetAttribLocation(shaderProgramId, attribute);
if (location == -1) {
throw new IllegalArgumentException("Could not find attribute: "
+ attribute + " for shaderProgramId: " + shaderProgramId);
} else {
return location;
}
}
public static void main(String[] args) {
new StandaloneMultiTextureExample().run();
}
}
You're setting up your vertex arrays incorrectly, by binding the vertex buffer at the wrong time.
When you're using Vertex Array Objects (VAO) to render [1], the only time that the GL_ARRAY_BUFFER
binding is read is the glVertexAttribPointer
call. Calling glVertexAttribPointer
takes the name of the currently bound GL_ARRAY_BUFFER
object and associates it with the attribute and VAO; after that, the GL_ARRAY_BUFFER
binding doesn't matter at all, and binding another array buffer will not modify the VAO in any way.
In your code, you call specifyVertexAttributes
to set up your VAO before you call glBindBuffer
. This means that the array buffer that glVertexAttribPointer
saves is the previous one used. In your first two examples, where you only ever bind one array buffer at any point of time, it "works" because the bound buffer from the previous frame persists and is read in the next frame; if you paused your program on the very first frame, it will likely be black.
The solution in your case is simple; move the glBindBuffer
call above the specifyVertexAttributes
call, so that your glVertexAttribPointer
calls read the proper buffer.
Note that this does not apply with the GL_ELEMENT_ARRAY_BUFFER
binding; the binding is saved in the VAO whenever you bind a new one.
[1] You're technically using the default VAO, which is only supported in a compatibility context, though its easy to create and bind a global VAO that's used all the time.