Hi there !
I'm currently making my own little 2d engine based on LWJGL. But the problems arrive quick and I have problems with the textures.
I load my file and create an OpenGL texture from it (the file exist) but when I render it it's a white box !
There is my code :
Texture.java
package net.parallelworlds.core;
import org.lwjgl.BufferUtils;
import org.lwjgl.opengl.GL12;
import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.nio.ByteBuffer;
import static org.lwjgl.opengl.GL11.*;
/**
* Created by nanoc_000 on 22/10/2014.
*/
public class Texture {
private static final int BYTES_PER_PIXEL = 4;
private final int textureId;
// Create a texture from a BufferedImage
public Texture(BufferedImage image) {
int[] pixels = new int[image.getWidth() * image.getHeight()];
image.getRGB(0, 0, image.getWidth(), image.getHeight(), pixels, 0, image.getWidth());
ByteBuffer buffer = BufferUtils.createByteBuffer(image.getWidth() * image.getHeight() * BYTES_PER_PIXEL); //4 for RGBA, 3 for RGB
for(int y = 0; y < image.getHeight(); y++){
for(int x = 0; x < image.getWidth(); x++){
int pixel = pixels[y * image.getWidth() + x];
buffer.put((byte) ((pixel >> 16) & 0xFF)); // Red component
buffer.put((byte) ((pixel >> 8) & 0xFF)); // Green component
buffer.put((byte) (pixel & 0xFF)); // Blue component
buffer.put((byte) ((pixel >> 24) & 0xFF)); // Alpha component. Only for RGBA
}
}
buffer.flip(); //FOR THE LOVE OF GOD DO NOT FORGET THIS
// You now have a ByteBuffer filled with the color data of each pixel.
// Now just create a texture ID and bind it. Then you can load it using
// whatever OpenGL method you want, for example:
textureId = glGenTextures(); //Generate texture ID
glBindTexture(GL_TEXTURE_2D, textureId); //Bind texture ID
//Setup wrap mode
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
//Setup texture scaling filtering
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
//Send texel data to OpenGL
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, image.getWidth(), image.getHeight(), 0, GL_RGBA, GL_UNSIGNED_BYTE, buffer);
glBindTexture(GL_TEXTURE_2D, 0);
}
// Load a BufferedImage from a file outside the jar
public static BufferedImage loadImageOutJar(String loc) throws IOException {
return ImageIO.read(new File(loc));
}
// Load a BufferedImage from a file inside the jar
public static BufferedImage loadImageInJar(String loc) throws IOException {
return ImageIO.read(Texture.class.getResource(loc));
}
public static BufferedImage loadImage(String loc) throws IOException {
try {
return loadImageInJar(loc);
} catch (IOException e) {
return loadImageOutJar(loc);
}
}
// Bind the texture to use it with OpenGL
public void bind() {
glBindTexture(GL_TEXTURE_2D, textureId);
}
public void destroy() {
glDeleteTextures(textureId);
}
// Unbind any texture
public static void unbindTexture() {
glBindTexture(GL_TEXTURE_2D, 0);
}
}
Game.java
package net.parallelworlds.core;
import net.parallelworlds.tests.TestGameState;
import org.lwjgl.LWJGLException;
import org.lwjgl.Sys;
import org.lwjgl.opengl.Display;
import org.lwjgl.opengl.DisplayMode;
import org.lwjgl.opengl.GL11;
import org.lwjgl.util.Color;
import org.lwjgl.util.Rectangle;
import static org.lwjgl.opengl.GL11.*;
/**
* Created by nanoc_000 on 20/10/2014.
*/
// Main class of the game
public class Game {
public static final void main(String[] args) {
Game.GetInstance().setGameState(new TestGameState());
Game.GetInstance().run();
}
// STATIC VARS //
private static Game GAME_INSTANCE = null;
public static final String GAME_NAME = "Parallel Worlds";
public static final String GAME_VERSION = "0.0.1";
public static final int DEFAULT_WIDTH = 600;
public static final int DEFAULT_HEIGHT = 800;
public static final int TILES_PER_LINE = 16;
// GAME SYSTEM IMPORTANT VARS //
private float lastFrameTime; // Time at the last frame
private int fpsCounter = 0; // FPS counter
private int fps = 0; // Number of frames per second
private float lastFPSTime; // Timer for FPS calculation
private boolean running = false; // Is the game running ?
// GAME GRAPHICS VARS //
private Color clearColor = (Color) Color.BLACK;
private GameState gameState = null;
private GameState nextGameState = null;
// USEFUL STATIC FUNCTIONS //
public static Game GetInstance() {
if(GAME_INSTANCE == null) GAME_INSTANCE = new Game();
return GAME_INSTANCE;
}
public static float[] getFloatColorValues(Color color) {
float[] colors = new float[4];
colors[0] = ((float) color.getRed()) / 255.f;
colors[1] = ((float) color.getGreen()) / 255.f;
colors[2] = ((float) color.getBlue()) / 255.f;
colors[3] = ((float) color.getAlpha()) / 255.f;
return colors;
}
// -------------------------- //
// HERE THE HARD WORD START :D //
// Private constructor for singleton
private Game() { }
// Start the game
public void run() {
// Prevent for running the game init / loop twice
if(!running) {
try {
Display.setDisplayMode(new DisplayMode(800, 600));
Display.setResizable(true);
Display.create();
} catch (LWJGLException e) {
e.printStackTrace();
System.exit(0);
}
// Initialisation
computeDelta();
lastFPSTime = getTime();
running = true;
glEnable(GL_TEXTURE_2D);
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
// Game loop
while (!Display.isCloseRequested() && running) {
gameLoop();
}
gameState.destroy();
// Destroy OpenGL
Display.destroy();
}
}
// Main loop of the game
private void gameLoop() {
// Load the next GameState in OpenGL context
if(nextGameState != null) {
if(gameState != null) gameState.destroy();
gameState = nextGameState;
gameState.create();
nextGameState.destroy();
}
float delta = computeDelta();
updateTitle();
updateFPS();
update(delta);
render();
Display.update();
Display.sync(60);
}
// Update the game
private void update(float delta) {
// Update the game state
if(gameState != null)
gameState.update(delta);
}
// Render the game
private void render() {
// Clearing the screen
float[] color = getFloatColorValues(clearColor);
glClearColor(color[0], color[1], color[2], color[3]);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
// Render the game state
if(gameState != null)
gameState.render();
}
// Calculate the new delta time between the last frame and now
private float computeDelta() {
float time = getTime();
float delta = time - lastFrameTime;
lastFrameTime = time;
return delta;
}
// Return the time in second
public float getTime() {
long ms = (Sys.getTime() * 1000) / Sys.getTimerResolution();
return ((float) ms) / 1000.f;
}
// Update the FPS counter
private void updateFPS() {
if(getTime() - lastFPSTime > 1.f) {
fps = fpsCounter;
fpsCounter = 0;
lastFPSTime += 1.f;
}
fpsCounter++;
}
// Tell is the game is running or not
public boolean isRunning() { return running; }
// Stop the game at the next frame
public void stop() { running = false; }
// Update the window title
private void updateTitle() {
String state = "NOSTATE";
if (gameState != null) state = gameState.getStateID();
Display.setTitle(GAME_NAME + " " + GAME_VERSION + " - " + state + " ("+ fps + " FPS)");
}
// Set the color used to clear the screen (maybe background ?)
public void setClearColor(Color color) { clearColor = color; }
// Return the color used to clear the screen
public Color getClearColor() { return clearColor; }
// Set the next game state
public void setGameState(GameState state) { nextGameState = state; }
// Return the actual game state
public GameState getGameState() { return gameState; }
// Set the render zone of the window
public void setRenderZone(double posX, double posY, double width, double height) {
if(width <= 0 || height <= 0) throw new IllegalArgumentException("Width or height must be > 0");
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
glOrtho(posX, posX + width, posY, posY + height, 1, -1);
glMatrixMode(GL_MODELVIEW);
}
}
TestGameState :
package net.parallelworlds.tests;
import net.parallelworlds.core.Game;
import net.parallelworlds.core.GameState;
import net.parallelworlds.core.Texture;
import org.lwjgl.Sys;
import org.lwjgl.util.Color;
import java.io.IOException;
import static org.lwjgl.opengl.GL11.*;
/**
* Created by nanoc_000 on 23/10/2014.
*/
public class TestGameState implements GameState {
private Texture testTexture;
@Override
public String getStateID() {
return "TEST";
}
@Override
public void create() {
//Game.GetInstance().setClearColor((Color) Color.RED);
Game.GetInstance().setRenderZone(-5, -5, 10, 10);
try {
testTexture = new Texture(Texture.loadImageOutJar("assets/gfx/font.png"));
} catch (IOException e) {
e.printStackTrace();
System.exit(1);
}
}
@Override
public void destroy() {
testTexture.destroy();
}
@Override
public void update(float delta) {
}
@Override
public void render() {
testTexture.bind();
glBegin(GL_QUADS);
glTexCoord2f(1.0f,1.0f); glVertex2f(-1.0f, 1.0f);
glTexCoord2f(1.0f,0.0f); glVertex2f(1.0f, 1.0f);
glTexCoord2f(0.0f,0.0f); glVertex2f(1.0f, -1.0f);
glTexCoord2f(0.0f,1.0f); glVertex2f(-1.0f, -1.0f);
glEnd();
}
}
It renders a white box on a black background instead of my texture.
Hope someone will find out the problem.
Good afternoon.
1) Have you enabled texturing?
2) Have you enabled lighting?
3) Are you using custom shaders?
1 & 2 : How do I enable them ?
3 : Not at the moment I'm working on the basics for now
You have all the code used here. GameState is just an interface.
You enable textures the same way you enable everything else in OpenGL too. You use glEnable with the apropriate gl enum constant.
You should read the documentation, you can find it online.
I enable it in the run() method
I don't know if this will help but this is what I setup pre render for my 2d drawing
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
glDisable(GL_CULL_FACE);
glDisable(GL_DEPTH_TEST);
and this is what I use at the beginning of each render cycle
glClear(GL_COLOR_BUFFER_BIT);
The secret is you don't want any depth testing in 2D so disable it and don't clear the buffer, I can't remember why I needed the BLEND enablement though, but I know it works with this setup
I set the setup as yours and it don't work :/ I also tried with a non transparent image (JPEG) but it's the same result. I think it's from the texture loading but the loading itself is aparently a well known code and I used it before.
Edit : Aparently this part of the code is fine. Don't know why it act like that :/
My bad it works. When I copied your setup I forgot to enable GL_TEXTURE_2D again --'
Is slick-util free to use in free or commercial projects ?