So, I'm using LWJGL and OpenGL to render some quads and textures. My quads have been working fine, until the addition of some textures. Specifically, when I added the ability to render text to the screen, all of the quads color has changed to black. I've looked around for a solution but have had no luck, the most common answers are to use glEnable(GL_TEXTURE_2D);
only when I draw textures, otherwise keep it disabled when drawing quads. This doesn't seem to be working for me, my quads are still black. I've included my Renderer, Font, and Texture classes below as I'm fairly certain the issue is somewhere in there.
Edit: So I've done a bit more testing, I wasn't even trying to draw normal textures (from .png files) however, now that I've tried to do that as well, I've found they also are drawn completely black. So as of now, the only thing rendering correctly is my font/text, even though I create a texture to render them as well
Renderer.java
public class Renderer {
private VertexArrayObject vao;
private VertexBufferObject vbo;
private ShaderProgram shaderProgram;
private FloatBuffer vertices;
private int numVertices;
private boolean drawing;
private Font fontSketchalot;
public void drawQuad(float x, float y, float width, float height, Colors c) {
glDisable(GL_TEXTURE_2D);
glBindTexture(GL_TEXTURE_2D, 0);
begin();
if (vertices.remaining() < 5 * 7) flush();
// Calculate Vertex positions
float x1 = x;
float y1 = y;
float x2 = x + width;
float y2 = y - height;
// Calculate color
float r = c.getR();
float g = c.getG();
float b = c.getB();
// Put data into buffer
vertices.put(x1).put(y1).put(r).put(g).put(b).put(0f).put(1f);
vertices.put(x1).put(y2).put(r).put(g).put(b).put(0f).put(0f);
vertices.put(x2).put(y2).put(r).put(g).put(b).put(1f).put(0f);
vertices.put(x2).put(y1).put(r).put(g).put(b).put(1f).put(1f);
// We drew X vertices
numVertices += 4;
end();
}
public void drawTextureFromTexture(Texture texture, float x, float y, float width, float height) {
drawTexture(texture, x, y, x + width, y - height, 0, 0, 1, 1, Colors.white);
}
public void drawTextureFromFont(Texture texture, float x, float y, float width, float height, float s1, float t1, float sWidth, float sHeight, Colors c) {
drawTexture(texture, x, y, x + width, y - height, s1, t1, s1 + sWidth, t1 + sHeight, c);
}
public void drawTexture(Texture texture, float x1, float y1, float x2, float y2, float s1, float t1, float s2, float t2, Colors c) {
glEnable(GL_TEXTURE_2D);
texture.prepare();
begin();
if (vertices.remaining() < 5 * 7) flush();
// Calculate color
float r = c.getR();
float g = c.getG();
float b = c.getB();
// Put data into buffer
vertices.put(x1).put(y1).put(r).put(g).put(b).put(s1).put(t2);
vertices.put(x1).put(y2).put(r).put(g).put(b).put(s1).put(t1);
vertices.put(x2).put(y2).put(r).put(g).put(b).put(s2).put(t1);
vertices.put(x2).put(y1).put(r).put(g).put(b).put(s2).put(t2);
// We drew X vertices
numVertices += 4;
end();
texture.unbind();
}
public void drawText(String text, float x, float y, float scale, Colors c) {
fontSketchalot.drawText(this, text, x, y, scale, c);
}
// Initialize renderer
public void init(){
// Create font
fontSketchalot = new Font(Fonts.SKETCHALOT);
// Set up shader programs
setupShaderProgram();
// Set wrapping and filtering values
setParameter(GL_TEXTURE_WRAP_S, GL_CLAMP_TO_BORDER);
setParameter(GL_TEXTURE_WRAP_T, GL_CLAMP_TO_BORDER);
setParameter(GL_TEXTURE_MIN_FILTER, GL_NEAREST);
setParameter(GL_TEXTURE_MAG_FILTER, GL_NEAREST);
// Enable blending (?????)
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
}
// Set parameter of texture
private void setParameter(int name, int value) {
glTexParameteri(GL_TEXTURE_2D, name, value);
}
// Clears drawing area
public void clear() {
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
}
// Begin rendering
public void begin() {
if (drawing) throw new IllegalStateException("Renderer is already drawing.");
drawing = true;
numVertices = 0;
}
// End rendering
public void end() {
if (!drawing) throw new IllegalStateException("Renderer is not drawing.");
drawing = false;
flush();
}
// Flushes data to GPU to get rendered
public void flush() {
if (numVertices > 0) {
vertices.flip();
if (vao != null) vao.bind();
else vbo.bind(GL_ARRAY_BUFFER);
specifyVertexAttributes();
}
shaderProgram.use();
// Upload the new vertex data
vbo.bind(GL_ARRAY_BUFFER);
vbo.uploadSubData(GL_ARRAY_BUFFER, 0, vertices);
// Draw batch
glDrawArrays(GL_QUADS, 0, numVertices);
// Clear vertex data for next batch
vertices.clear();
numVertices = 0;
}
private void setupShaderProgram() {
// Generate VertexArrayObject
if (Game.is32Supported()) {
vao = new VertexArrayObject();
vao.bind();
} else {
throw new RuntimeException("OpenGL 3.2 not supported.");
}
// Generate VertexBufferObject
vbo = new VertexBufferObject();
vbo.bind(GL_ARRAY_BUFFER);
// Create FloatBuffer
vertices = MemoryUtil.memAllocFloat(4096);
// Upload null data to allocate storage for the VBO
long size = vertices.capacity() * Float.BYTES;
vbo.uploadData(GL_ARRAY_BUFFER, size, GL_DYNAMIC_DRAW);
// Initialize variables
numVertices = 0;
drawing = false;
// Load Shaders:
Shader vertexShader, fragmentShader;
if (Game.is32Supported()) {
vertexShader = Shader.loadShader(GL_VERTEX_SHADER, "res/shaders/default.vert");
fragmentShader = Shader.loadShader(GL_FRAGMENT_SHADER, "res/shaders/default.frag");
} else {
throw new RuntimeException("OpenGL 3.2 not supported.");
}
// Create ShaderProgram
shaderProgram = new ShaderProgram();
shaderProgram.attachShader(vertexShader);
shaderProgram.attachShader(fragmentShader);
if (Game.is32Supported()) {
shaderProgram.bindFragmentDataLocation(0, "fragColor");
}
shaderProgram.link();
shaderProgram.use();
// Delete linked shaders
vertexShader.delete();
fragmentShader.delete();
// Get width & height of framebuffer
long window = GLFW.glfwGetCurrentContext();
int width, height;
try (MemoryStack stack = MemoryStack.stackPush()) {
IntBuffer widthBuffer = stack.mallocInt(1);
IntBuffer heightBuffer = stack.mallocInt(1);
GLFW.glfwGetFramebufferSize(window, widthBuffer, heightBuffer);
width = widthBuffer.get();
height = heightBuffer.get();
}
// Specify vertex pointers
specifyVertexAttributes();
// Set Model Matrix to identity matrix
Matrix4f model = new Matrix4f();
int uniModel = shaderProgram.getUniformLocation("model");
shaderProgram.setUniform(uniModel, model);
// Set View Matrix to identity matrix
Matrix4f view = new Matrix4f();
int uniView = shaderProgram.getUniformLocation("view");
shaderProgram.setUniform(uniView, view);
// Set Projection Matrix to an orthographic projection
Matrix4f projection = Matrix4f.orthographic(0f, width, 0f, height, -1f, 1f);
int uniProjection = shaderProgram.getUniformLocation("projection");
shaderProgram.setUniform(uniProjection, projection);
}
// Specifies the vertex shader pointers (attributes)
private void specifyVertexAttributes() {
int posAttrib = shaderProgram.getAttributeLocation("position");
shaderProgram.enableVertexAttribute(posAttrib);
shaderProgram.pointVertexAttribute(posAttrib, 2, 7 * Float.BYTES, 0);
int colAttrib = shaderProgram.getAttributeLocation("color");
shaderProgram.enableVertexAttribute(colAttrib);
shaderProgram.pointVertexAttribute(colAttrib, 3, 7 * Float.BYTES, 2 * Float.BYTES);
int texAttrib = shaderProgram.getAttributeLocation("texcoord");
shaderProgram.enableVertexAttribute(texAttrib);
shaderProgram.pointVertexAttribute(texAttrib, 2, 7 * Float.BYTES, 5 * Float.BYTES);
int uniTex = shaderProgram.getUniformLocation("texImage");
shaderProgram.setUniform(uniTex, 0);
}
}
Font.java
public class Font {
private String fontPath;
private java.awt.Font font;
private Map<Character, Glyph> glyphs;
private float fontHeight;
private Texture texture;
public Font(String fontPath) {
System.out.println("Creating font...");
this.fontPath = fontPath;
loadFont();
glyphs = new HashMap<>();
texture = createFontTexture();
System.out.println("Font created.");
}
public Font() {
System.out.println("Creating font...");
this.fontPath = "DEFAULT";
font = new java.awt.Font(java.awt.Font.MONOSPACED, java.awt.Font.PLAIN, 30);
glyphs = new HashMap<>();
texture = createFontTexture();
System.out.println("Font created.");
}
private void loadFont() {
try {
System.out.println("Loading font...");
font = java.awt.Font.createFont(java.awt.Font.TRUETYPE_FONT, new FileInputStream(fontPath)).deriveFont(java.awt.Font.PLAIN, 30);
System.out.println("Font loaded.");
} catch (Exception e) {
throw new RuntimeException("Could not load font.");
}
}
private Texture createFontTexture() {
int imageWidth = 0;
int imageHeight = 0;
// Add up total width and height
for (int i = 32; i < 256; i++) {
if (i == 127) { // DEL control code
continue;
}
char c = (char) i;
BufferedImage ch = createCharImage(c);
if (ch == null) {
System.out.println("Could not load [CHAR: \"" + c + "\"] from font: " + fontPath);
continue;
}
imageWidth += ch.getWidth();
imageHeight = Math.max(imageHeight, ch.getHeight());
}
fontHeight = Converter.glfwCoordToOpenGLCoord(imageHeight);
/* Image for the texture */
BufferedImage image = new BufferedImage(imageWidth, imageHeight, BufferedImage.TYPE_INT_ARGB);
Graphics2D g = image.createGraphics();
// Set up glyphs (Map<CHAR, GLYPH>)
int xOffsetI = 0;
float xOffsetF = 0;
for (int i = 32; i < 256; i++) {
if (i == 127) { // DEL control code
continue;
}
char c = (char) i;
BufferedImage charImage = createCharImage(c);
if (charImage == null) {
System.out.println("Could not load [CHAR: \"" + c + "\"] from font: " + fontPath);
continue;
}
int charWidth = charImage.getWidth();
int charHeight = charImage.getHeight();
// Create glyph
Glyph ch = new Glyph(Converter.glfwCoordToOpenGLCoord(charWidth), Converter.glfwCoordToOpenGLCoord(charHeight), ((float)charWidth / (float)imageWidth), (float)charHeight / (float)imageHeight, xOffsetF, 0.0f);
// Draw char on image
g.drawImage(charImage, xOffsetI, 0, null);
xOffsetI += charWidth;
xOffsetF += ch.sWidth;
// Put in map
glyphs.put(c, ch);
}
// Flip image Horizontal to get the origin to bottom left
AffineTransform transform = AffineTransform.getScaleInstance(1f, -1f);
transform.translate(0, -image.getHeight());
AffineTransformOp operation = new AffineTransformOp(transform, AffineTransformOp.TYPE_NEAREST_NEIGHBOR);
image = operation.filter(image, null);
// Get char width & char height of image
int width = image.getWidth();
int height = image.getHeight();
// Put pixel data into int[] pixels
int[] pixels = new int[width * height];
image.getRGB(0, 0, width, height, pixels, 0, width);
// Put pixel data into byte buffer
ByteBuffer buffer = MemoryUtil.memAlloc(width * height * 4);
for (int i = 0; i < height; i++){
for (int j = 0; j < width; j++) {
// Pixel format : OxAARRGGBB
int pixel = pixels[i * width + j];
buffer.put((byte) ((pixel >> 16) & 0xFF)); // 0x000000RR
buffer.put((byte) ((pixel >> 8) & 0xFF)); // 0x000000GG
buffer.put((byte) ((pixel) & 0xFF)); // 0x000000BB
buffer.put((byte) ((pixel >> 24) & 0xFF)); // 0x000000AA
//buffer.put((byte)(0xFF)); // Test
}
}
buffer.flip(); // Set index back to 0
Texture fontTexture = Texture.createTexture(width, height, buffer); // Create texture
MemoryUtil.memFree(buffer); // Free buffer
return fontTexture;
}
private BufferedImage createCharImage(char c) {
// Begin by calculating proper width and height
BufferedImage image = new BufferedImage(1, 1, BufferedImage.TYPE_INT_ARGB);
Graphics2D g = image.createGraphics();
if (Options.ANTIALIAS) {
g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
}
g.setFont(font);
FontMetrics fontMetrics = g.getFontMetrics();
g.dispose();
int charWidth = fontMetrics.charWidth(c);
int charHeight = fontMetrics.getHeight();
if (charWidth == 0) return null;
// Now set up the image
image = new BufferedImage(charWidth, charHeight, BufferedImage.TYPE_INT_ARGB);
g = image.createGraphics();
if (Options.ANTIALIAS) {
g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
}
g.setFont(font);
g.setPaint(Color.WHITE); // Use white so we can set the color while rendering
g.drawString(String.valueOf(c), 0, fontMetrics.getAscent()); // Paint char onto image
g.dispose();
// Finally return the final image
return image;
}
public void drawText(Renderer renderer, CharSequence text, float x, float y, float scale, Colors c) {
float xOffset = 0;
float yOffset = 0;
texture.prepare();
//renderer.begin();
for (int i = 0; i < text.length(); i++) {
char ch = text.charAt(i);
if (ch == '\n') {
// If we start a new line, change the yOffset to reflect (then continue)
yOffset -= fontHeight;
continue;
}
if (ch == '\r') {
// Skip carriage return
continue;
}
Glyph g = glyphs.get(ch);
renderer.drawTextureFromFont(texture, x + xOffset, y + yOffset, g.width * scale, g.height * scale, g.s, g.t, g.sWidth, g.sHeight, c);
xOffset += g.width * scale;
}
//renderer.end();
texture.unbind();
}
}
Texture.java
public class Texture {
// Store handle
private final int id;
private int width;
private int height;
private ByteBuffer image;
// Draw texture using given renderer
public void draw(Renderer renderer, float x, float y, float scale) {
// Scale texture
float w = ((float)width * scale * 2) / (Game.WIDTH);
float h = ((float)height * scale * 2) / (Game.HEIGHT);
renderer.drawTextureFromTexture(this, x, y, w, h);
}
// Create new texture
public Texture() {
id = glGenTextures();
}
// Prepare texture to be drawn
public void prepare() {
bind();
uploadData(width, height, image);
}
// Bind texture
public void bind() {
glBindTexture(GL_TEXTURE_2D, id);
}
// Set parameter of texture
private void setParameter(int name, int value) {
glTexParameteri(GL_TEXTURE_2D, name, value);
}
// Upload image data with specified width and height
public void uploadData(int width, int height, ByteBuffer data) {
uploadData(GL_RGBA8, width, height, GL_RGBA, data);
}
// Upload image data with specified internal format, width, height, and image format
public void uploadData(int internalFormat, int width, int height, int format, ByteBuffer data) {
glTexImage2D(GL_TEXTURE_2D, 0, internalFormat, width, height, 0, format, GL_UNSIGNED_BYTE, data);
}
// Delete texture
public void delete() {
glDeleteTextures(id);
}
// Get width
public int getWidth() {
return width;
}
// Set width
public void setWidth(int width) {
if (width > 0) {
this.width = width;
}
}
// Get height
public int getHeight() {
return height;
}
// Set height
public void setHeight(int height) {
if (height > 0) {
this.height = height;
}
}
// Set image
public void setImage(ByteBuffer image) {
this.image = image;
}
public static Texture createTexture(int width, int height, ByteBuffer image) {
Texture texture = new Texture();
texture.setWidth(width);
texture.setHeight(height);
texture.setImage(image);
texture.prepare();
return texture;
}
public static Texture loadTexture(String path) {
ByteBuffer image;
int width, height;
try (MemoryStack stack = MemoryStack.stackPush()) {
IntBuffer w = stack.mallocInt(1);
IntBuffer h = stack.mallocInt(1);
IntBuffer comp = stack.mallocInt(1);
// Load image
stbi_set_flip_vertically_on_load(true);
image = stbi_load(path, w, h, comp, 4);
if (image == null) throw new RuntimeException("Could not load texture.");
width = w.get();
height = h.get();
}
return createTexture(width, height, image);
}
public void unbind() {
glBindTexture(GL_TEXTURE_2D, 0);
}
}
User contributions licensed under CC BY-SA 3.0