[Newb Question] Rendering textured quads just renders them white?

Started by IceMetalPunk, January 10, 2016, 03:11:00

Previous topic - Next topic

IceMetalPunk

I'm extremely new to LWJGL, and more importantly, to graphics rendering in general (though I am by no means new to programming). As such, I've built a lot of my project's data backbone and avoided rendering, but now I need to start learning how to render graphics.

I followed the tutorial "The Quad Textured" on the LWJGL wiki, modified to fit my project, and I've run into a persistent problem: although I'm successfully loading a texture in from a PNG and binding it, etc. (i.e. I'm getting a positive texture ID and no errors), the quads are being drawn completely white rather than taking on the texture of the image.

Rather than copy/paste my entire project here, I'll post the code I believe is relevant; feel free to ask if there's something more you need.

QuadRenderer.java (the quad rendering class):
package com.IceMetalPunk.learnLWJGL.render;

import java.nio.ByteBuffer;
import java.nio.FloatBuffer;

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

public class QuadRenderer {
	public Vertex[] vertices;
	public FloatBuffer vertexBuffer;
	int vaoID, vboID, indexVBOID, indexCount;

	/*
	 * Constructors
	 * 
	 * Coordinates are for vertices clockwise from bottom-left.
	 */
	public QuadRenderer(float x1, float y1, float z1, float x2, float y2, float z2, float x3, float y3, float z3,
			float x4, float y4, float z4) {
		this.vertices = new Vertex[] { new Vertex(), new Vertex(), new Vertex(), new Vertex() };
		this.vertices[0].setXYZ(x1, y1, z1);
		this.vertices[0].setUV(0f, 0f);
		this.vertices[1].setXYZ(x2, y2, z2);
		this.vertices[1].setUV(0f, 1f);
		this.vertices[2].setXYZ(x3, y3, z3);
		this.vertices[2].setUV(1f, 1f);
		this.vertices[3].setXYZ(x4, y4, z4);
		this.vertices[3].setUV(1f, 0f);

		// Get vertex data into a single buffer of floats in the proper order
		this.vertexBuffer = BufferUtils.createFloatBuffer(this.vertices.length * Vertex.numElements);
		for (int i = 0; i < this.vertices.length; ++i) {
			this.vertexBuffer.put(this.vertices[i].get());
		}

		// OpenGL uses CC order; vertexes above specified in clockwise order; so
		// flip
		this.vertexBuffer.flip();

		// Specify OpenGL triangle-list drawing order
		byte[] drawOrder = new byte[] { 0, 1, 2, 2, 3, 0 };
		this.indexCount = drawOrder.length;
		ByteBuffer drawOrderBuffer = BufferUtils.createByteBuffer(this.indexCount);
		drawOrderBuffer.put(drawOrder);
		drawOrderBuffer.flip();

		// Create a Vertex Array Object (VAO) and bind/select it
		this.vaoID = GL30.glGenVertexArrays();
		GL30.glBindVertexArray(this.vaoID);

		// Create a Vertex Buffer Array (VBO) and bind/select it
		this.vboID = GL15.glGenBuffers();
		GL15.glBindBuffer(GL15.GL_ARRAY_BUFFER, vboID);

		// Put the vertex data into the drawing buffer for the selected VBO
		GL15.glBufferData(GL15.GL_ARRAY_BUFFER, this.vertexBuffer, GL15.GL_STATIC_DRAW);

		// Put the vertex data into the buffers for the selected VAO.
		// This specifies which byte offsets in the VBO correspond to position,
		// color, and UV of the vertex.
		// The "false" parameter here specifies whether the values should be
		// normalized; no.
		GL20.glVertexAttribPointer(0, Vertex.positionElements, GL11.GL_FLOAT, false, Vertex.size, Vertex.positionOffset);
		GL20.glVertexAttribPointer(1, Vertex.colorElements, GL11.GL_FLOAT, false, Vertex.size, Vertex.colorOffset);
		GL20.glVertexAttribPointer(2, Vertex.uvElements, GL11.GL_FLOAT, false, Vertex.size, Vertex.uvOffset);

		// Deselect (unbind) the VBO and VAO
		GL15.glBindBuffer(GL15.GL_ARRAY_BUFFER, 0);
		GL30.glBindVertexArray(0);

		// Create a new VBO for the draw order array, bind it, put the index
		// data in, then unbind it
		this.indexVBOID = GL15.glGenBuffers();
		GL15.glBindBuffer(GL15.GL_ELEMENT_ARRAY_BUFFER, this.indexVBOID);
		GL15.glBufferData(GL15.GL_ELEMENT_ARRAY_BUFFER, drawOrderBuffer, GL15.GL_STATIC_DRAW);
		GL15.glBindBuffer(GL15.GL_ELEMENT_ARRAY_BUFFER, 0);
	}

	/* "Destructors" */
	public void destroy() {
		// Disable the VAO attribute lists, just in case they're enabled
		GL30.glBindVertexArray(this.vaoID);
		GL20.glDisableVertexAttribArray(0);
		GL20.glDisableVertexAttribArray(1);
		GL20.glDisableVertexAttribArray(2);

		// Unbind (deselect) VBO and delete it
		GL15.glBindBuffer(GL15.GL_ARRAY_BUFFER, 0);
		GL15.glDeleteBuffers(this.vboID);

		// Unbind (deselect) the draw order VBO and delete it
		GL15.glBindBuffer(GL15.GL_ELEMENT_ARRAY_BUFFER, 0);
		GL15.glDeleteBuffers(this.indexVBOID);

		// Unbind (deselect) and delete the VAO
		GL30.glBindVertexArray(0);
		GL30.glDeleteVertexArrays(this.vaoID);
	}

	// Just in case!
	public void finalize() {
		this.destroy();
	}

	/* Rendering! */
	public void render(int textureID) {

		// Use texture unit 0 and bind/select the given texture
		GL13.glActiveTexture(GL13.GL_TEXTURE0);
		GL11.glBindTexture(GL11.GL_TEXTURE_2D, textureID);

		// Bind the vertex attribute array and enable the position, color, and
		// UV attributes
		GL30.glBindVertexArray(this.vaoID);
		GL20.glEnableVertexAttribArray(0); // Position
		GL20.glEnableVertexAttribArray(1); // Color
		GL20.glEnableVertexAttribArray(2); // UV

		// Bind our index VBO to the element array buffer
		GL15.glBindBuffer(GL15.GL_ELEMENT_ARRAY_BUFFER, this.indexVBOID);

		// Draw the vertices! Last parameter is the offset in the buffer to
		// start at.
		GL11.glDrawElements(GL11.GL_TRIANGLES, this.indexCount, GL11.GL_UNSIGNED_BYTE, 0);

		// Cleanup: Deselect (unbind) everything and disable attribute lists
		GL15.glBindBuffer(GL15.GL_ELEMENT_ARRAY_BUFFER, 0);
		GL20.glDisableVertexAttribArray(0);
		GL20.glDisableVertexAttribArray(1);
		GL20.glDisableVertexAttribArray(2);
		GL30.glBindVertexArray(0);
	}
}


Vertex.java:
package com.IceMetalPunk.learnLWJGL.render;

public class Vertex {
	private float[] xyzw = new float[] { 0f, 0f, 0f, 1f };
	private float[] rgba = new float[] { 1f, 1f, 1f, 1f };
	private float[] uv = new float[] { 0f, 0f };

	/* Some memory-management constants used when rendering from this data */
	public static final int positionElements = 4;
	public static final int colorElements = 4;
	public static final int uvElements = 2;
	public static final int numElements = positionElements + colorElements + uvElements;

	// There are 4 bytes per float in Java, on any platform
	public static final int bytesPerElement = 4;

	public static final int positionOffset = 0;
	public static final int colorOffset = positionElements * bytesPerElement;
	public static final int uvOffset = colorOffset + colorElements * bytesPerElement;

	public static final int size = bytesPerElement * (positionElements + colorElements + uvElements);

	/* Setters */
	public void setXYZ(float x, float y, float z) {
		this.setXYZW(x, y, z, 1f);
	}

	public void setXYZW(float x, float y, float z, float w) {
		this.xyzw = new float[] { x, y, z, w };
	}

	public void setUV(float u, float v) {
		this.uv = new float[] { u, v };
	}

	public void setRGB(float r, float g, float b) {
		this.setRGBA(r, g, b, this.rgba[3]);
	}

	public void setRGBA(float r, float g, float b, float a) {
		this.rgba = new float[] { r, g, b, a };
	}

	/* Getters */
	public float[] getRGBA() {
		return new float[] { this.rgba[0], this.rgba[1], this.rgba[2], this.rgba[3] };
	}

	public float[] getXYZW() {
		return new float[] { this.xyzw[0], this.xyzw[1], this.xyzw[2], this.xyzw[3] };
	}

	public float[] getUV() {
		return new float[] { this.uv[0], this.uv[1] };
	}

	// Gets *everything* in one array
	public float[] get() {
		float[] ret = new float[numElements];
		int pos, col, tex;

		for (pos = 0; pos < this.xyzw.length; ++pos) {
			ret[pos] = this.xyzw[pos];
		}
		for (col = 0; col < this.rgba.length; ++col) {
			ret[pos + col] = this.rgba[col];
		}
		for (tex = 0; tex < this.uv.length; ++tex) {
			ret[pos + col + tex] = this.uv[tex];
		}
		return ret;
	}
}


Renderer#textureFromPNG() method (creates a texture given a PNG image's filename):
// Load textures from PNG files and return their handle IDs
	public int textureFromPNG(String filename) {
		GL.createCapabilities();
		FileInputStream input = null;
		PNGDecoder png = null;
		ByteBuffer output = null;
		try {
			// Load PNG file into buffer
			input = new FileInputStream(filename);
		}
		catch (FileNotFoundException e1) {
			System.err.println("Texture not found: " + filename);
			return -1;
		}
		try {
			png = new PNGDecoder(input);
			// Setup output buffer of RGBA data
			output = ByteBuffer.allocateDirect(4 * png.getHeight() * png.getWidth());
			// Decode PNG to raw RGBA data
			png.decode(output, 4 * png.getWidth(), PNGDecoder.Format.RGBA);
			// Flip because PNGs are stored flipped
			output.flip();
			// Cleanup
			input.close();
		}
		catch (IOException e) {
			System.err.println("Texture could not be decoded as a PNG: " + filename);
			return -2;
		}

		// Allocate the next available texture handle ID
		int texID = GL11.glGenTextures();
		// Set texture unit 0 for our work?
		GL13.glActiveTexture(GL13.GL_TEXTURE0);
		// Bind the soon-to-be texture to our ID
		GL11.glBindTexture(GL11.GL_TEXTURE_2D, texID);
		// Indicate 1 byte per color component
		GL11.glPixelStorei(GL11.GL_UNPACK_ALIGNMENT, 1);
		// Create a texture from the RGBA data
		GL11.glTexImage2D(GL11.GL_TEXTURE_2D, 0, GL11.GL_RGBA, png.getWidth(), png.getHeight(), 0, GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE, output);
		// Generate mipmap for scaling the texture
		GL30.glGenerateMipmap(GL11.GL_TEXTURE_2D);
		// (S,T) is like (U,V) coords, and this sets up repeat wrapping on edges
		GL11.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_WRAP_S, GL11.GL_REPEAT);
		GL11.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_WRAP_T, GL11.GL_REPEAT);
		// Setup algorithms for magnifying and minimizing images
		// Nearest-neighbor sampling for +, mipmap for -
		GL11.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MAG_FILTER, GL11.GL_NEAREST);
		GL11.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MIN_FILTER, GL11.GL_LINEAR_MIPMAP_LINEAR);
		this.textureList.add(texID); // Add to a list for tracking and cleanup later; irrelevant, I think
		return texID;
	}


As I said, I mostly just followed a couple of tutorials and modified it for my project. So that's the relevant code; when I call QuadRenderer#render(), passing in a textureID generated from Renderer#textureFromPNG(), it renders a solid white square instead of a textured one. I tried playing around with the vertex RGBA and found it makes no difference; it's ignored and a solid white square is all I ever get rendered. (If it matters, the texture I'm using is a 32x32 brown dirt PNG.)

Where have I gone wrong?

Kai

That tutorial you mentioned is using a shader, so please show that "setupShaders()" method of yours and your vertex and fragment shader.
If you are not using a shader but the fixed-function pipeline, then you may not simply use glVertexAttribPointer, since OpenGL has no idea that index 2 means UV-coordinates. You would then use GL11.glTexCoordPointer for that. It is just a concidence that you actually even see a quad, because attribute index 0 is very frequently implicitly used for the vertex position attribute. Actually, you should have used GL11.glVertexPointer for that.
Then, when using a texture with the fixed-function pipeline, you must also enable texturing via: GL11.glEnable(GL_TEXTURE_2D)

IceMetalPunk

Ah, okay. I was under the (clearly wrong) assumption that the shaders and the textured quad rendering were independent pieces of that tutorial; I didn't realize they were so dependently linked. I'll look into finding tutorials on how to work with the fixed-function pipeline instead. Thank you!

abcdef

if you are starting out you should find tutorials on modern opengl as the fixed function pipe line is now deprecated.

This guy has a good set of tutorials but there are lots out there

http://antongerdelan.net/opengl/


Hydroque

https://www.youtube.com/user/ThinMatrix

ThinMatrix has a few dozen modern opengl game programming stuff in 3d that you can adapt to 2d.