I searched for Texture Implementations without the Slick Utils library.
I found 2 ways, to do this:
The first, saves the pixels with strange byteshifting in a byte buffer:
int loadTexture(){
try{
BufferedImage img = ImageIO.read(getClass().getClassLoader().getResourceAsStream("background.png"));
int pixels[] = new int[img.getWidth() * img.getHeight()];
img.getRGB(0, 0, img.getWidth(), img.getHeight(), pixels, 0, img.getWidth());
ByteBuffer buffer = BufferUtils.createByteBuffer(img.getWidth() * img.getHeight() * 3);
for(int x = 0; x < img.getWidth(); x++){
for(int y = 0; y < img.getHeight(); y++){
int pixel = pixels[y * img.getWidth() + x];
buffer.put((byte) ((pixel >> 16) & 0xFF));
buffer.put((byte) ((pixel >> 8) & 0xFF));
buffer.put((byte) (pixel & 0xFF));
}
}
buffer.flip();
int textureId = glGenTextures();
glBindTexture(GL_TEXTURE_2D, textureId);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL12.GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL12.GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB8, img.getWidth(), img.getHeight(), 0, GL_RGB, GL_UNSIGNED_BYTE, buffer);
return textureId;
}
catch(Exception e){
e.printStackTrace();
return 0;
}
}
This returns a texture id as well, and i haven't any idea how tu use this id.
The second way doesnt do any byteshifting, and uses a IntBuffer: Also, it is a ready class to save different textures with names and so on.
The Code of these:
ublic class TextureIO {
private final IntBuffer texture;
private final int width;
private final int height;
private int id;
public TextureIO(final InputStream inputStream) throws IOException {
BufferedImage image = ImageIO.read(inputStream);
width = image.getWidth();
height = image.getHeight();
final AffineTransform tx = AffineTransform.getScaleInstance(1, -1);
tx.translate(0, -height);
final AffineTransformOp op = new AffineTransformOp(tx, AffineTransformOp.TYPE_NEAREST_NEIGHBOR);
image = op.filter(image, null);
final int[] pixels = image.getRGB(0, 0, width, height, null, 0, width);
texture = BufferUtils.createIntBuffer(pixels.length);
texture.put(pixels);
texture.rewind();
}
public void init() {
GL11.glEnable(GL11.GL_TEXTURE_2D);
final IntBuffer buffer = BufferUtils.createIntBuffer(1);
GL11.glGenTextures(buffer);
id = buffer.get(0);
GL11.glBindTexture(GL11.GL_TEXTURE_2D, id);
GL11.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MAG_FILTER, GL11.GL_LINEAR);
GL11.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MIN_FILTER, GL11.GL_LINEAR);
GL11.glTexImage2D(GL11.GL_TEXTURE_2D, 0, GL11.GL_RGBA8, width, height, 0, GL12.GL_BGRA, GL12.GL_UNSIGNED_INT_8_8_8_8_REV, texture);
GL11.glBindTexture(GL11.GL_TEXTURE_2D, 0);
}
public void bind() {
GL11.glBindTexture(GL11.GL_TEXTURE_2D, id);
}
public void unbind() {
GL11.glBindTexture(GL11.GL_TEXTURE_2D, 0);
}
}
Im really new to lwjgl development, and want to know which version is better. Cause im a friend of implementing such things by myself, i want the lwjgl.jar to be the own library im using.
I read on different sites, the buffer.flip() method would be necassary. but why? And why the second version doesnt do this? Also, i want to understand the difference between this two implementations, what happens in the first and what in the second?
Thank you!
Both of those are pretty bad implementations IMO. I would recommend watching this video for a more standard approach. Although you would have to also use the PNGDecoder.jar library it is like an extension to the lwjgl library.
Related
I'm trying to create a texture from a loaded Buffered image like this:
public static long loadTexture(String img) throws IOException{
File imgPath = new File(img);
BufferedImage bufferedImage = ImageIO.read(imgPath);
byte[] pixels = ((DataBufferByte) bufferedImage.getRaster().getDataBuffer()).getData();
int id = glGenTextures();
glBindTexture(GL_TEXTURE_2D, id);
glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, bufferedImage.getWidth(), bufferedImage.getHeight(), 0, GL_RGBA, GL_UNSIGNED_BYTE, ByteBuffer.wrap(pixels));
glGenerateMipmap(GL_TEXTURE_2D);
return id;
}
But this code gives me a java sigsegv error. I am using a Java.nio.Bytebuffer because the sun one isn't supported in java 11.
So what am I doing wrong? The Image is loaded correctly, with 4bpp:
//last 2 digits are lenght
FF-AD-6F-CB-FF-FF-FF-00-FF-FF-FF-00-FF-FF-FF-00-FF-FF-FF-00-FF-00-00-FF-FF-00-00-FF-FF-FF-FF-00-FF-FF-FF-00-FF-00-00-FF-FF-00-00-FF-FF-FF-FF-00-FF-FF-FF-00-FF-FF-FF-00-FF-FF-FF-00-FF-FF-FF-00-64
Heres the image:
its pretty small of course but the data is correct.
So why am I getting a sigsegv? the log is pretty useless and long so I can't post it.
And how do I create an opengl texture from a 4bpp byte array?
As suggested in Java Code Examples for org.lwjgl.opengl.GL11.glTexImage2D() (Example 5) you have to copy the data to a ByteBuffer in loops:
public static long loadTexture(String img) throws IOException{
File imgPath = new File(img);
BufferedImage image = ImageIO.read(imgPath);
int[] pixels = new int[image.getWidth() * image.getHeight()];
image.getRGB(0, 0, image.getWidth(), image.getHeight(), pixels, 0, image.getWidth());
ByteBuffer buffer = ByteBuffer.allocateDirect(image.getWidth() * image.getHeight() * 4);
for(int h = 0; h < image.getHeight(); h++) {
for(int w = 0; w < image.getWidth(); w++) {
int pixel = pixels[h * image.getWidth() + w];
buffer.put((byte) ((pixel >> 16) & 0xFF));
buffer.put((byte) ((pixel >> 8) & 0xFF));
buffer.put((byte) (pixel & 0xFF));
buffer.put((byte) ((pixel >> 24) & 0xFF));
}
}
buffer.flip();
int id = glGenTextures();
glBindTexture(GL_TEXTURE_2D, id);
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, image.getWidth(), image.getHeight(),
0, GL_RGBA, GL_UNSIGNED_BYTE, buffer);
glGenerateMipmap(GL_TEXTURE_2D);
return id;
}
Note, glPixelStorei(GL_UNPACK_ALIGNMENT, 1) is unnecessary in that case, because the size of a RGBA pixel is 4 bytes, so each row is aligned to 4 bytes and GL_UNPACK_ALIGNMENT by default is 4.
Furthermore, if you want to use Mipmaps (glGenerateMipmap), then the minifying function (GL_TEXTURE_MIN_FILTER) has to be GL_NEAREST_MIPMAP_NEAREST, GL_LINEAR_MIPMAP_NEAREST, GL_NEAREST_MIPMAP_LINEAR or GL_LINEAR_MIPMAP_LINEAR. (See glTexParameter)
Trying to find a way to render a simple 2D image in Java using OpenGL I stumbled upon a more or less understandable class that does all of the image loading for you which I could not understand how to do. I believe it belongs to a person named Krythic and I assume that it works fine, I even preinted out the height and width of the image it is reading to see if it reads the proper image, which it does.
But here is said class nonetheless (I don't fully understand how it works):
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.nio.ByteBuffer;
import javax.imageio.ImageIO;
import org.lwjgl.BufferUtils;
import org.lwjgl.opengl.GL12;
import static org.lwjgl.opengl.GL11.*;
public class TextureLoader {
private static final int BYTES_PER_PIXEL = 4;//3 for RGB, 4 for RGBA
public static int loadTexture(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:
int textureID = glGenTextures(); //Generate texture ID
glBindTexture(GL_TEXTURE_2D, textureID); //Bind texture ID
//Setup wrap mode
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL12.GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL12.GL_CLAMP_TO_EDGE);
//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_RGBA8, image.getWidth(), image.getHeight(), 0, GL_RGBA, GL_UNSIGNED_BYTE, buffer);
//Return the texture ID so we can bind it later again
return textureID;
}
public static BufferedImage loadImage(String loc)
{
try {
return ImageIO.read(Main.class.getResource(loc));
} catch (IOException e) {
//Error Handling Here
}
return null;
}
And here is the code I wrote thinking it would render the image on a quad, and that is probably where the problem lies.
while ( glfwWindowShouldClose(window) == GL_FALSE ) {
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
BufferedImage image = TextureLoader.loadImage("test.png");
int textureID = TextureLoader.loadTexture(image);
glBindTexture(GL_TEXTURE_2D, textureID);
glBegin(GL_QUADS);
glTexCoord2f(0, 0);
glVertex2f(-1f, -1f);
glTexCoord2f(1, 0);
glVertex2f( 0f, -1f);
glTexCoord2f(1, 1);
glVertex2f( 0f, 0f);
glTexCoord2f(0, 1);
glVertex2f(-1f, 0f);
glEnd();
glfwSwapBuffers(window);
glfwPollEvents();
}
So it draws a quad in the lower left corner of the screen as it did before, but it's white and the texture is supposed to be brown. It doesn't give me any exceptions so I don't know exactly where I messed up.
Enable textures with
glEnable(GL_TEXTURE_2D);
I am making a game with LWJGL and by using openGL, I believe my best option is to use Textures and render them with quads. However, I can only seem to find information on loading a texture from an image where the entire image is only ONE texture. What I would like to do is read an entire spritesheet in and be able to separate it into different textures. Is there a somewhat simple way to do this?
You could load the image, from e.g. a .png file to a BufferedImage with
public static BufferedImage loadImage(String location)
{
try {
BufferedImage image = ImageIO.read(new File(location));
return image;
} catch (IOException e) {
System.out.println("Could not load texture: " + location);
}
return null;
}
Now you are able to call getSubimage(int x, int y, int w, int h) on that resulting BufferedImage, giving you the seperated part. You now just need to create a Texture of the BufferedImage. This code should do the work:
public static int loadTexture(BufferedImage image){
if (image == null) {
return 0;
}
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:
int textureID = glGenTextures();
glBindTexture(GL_TEXTURE_2D, textureID);
//setup wrap mode
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL12.GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL12.GL_CLAMP_TO_EDGE);
//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_RGBA8, image.getWidth(), image.getHeight(), 0, GL_RGBA, GL_UNSIGNED_BYTE, buffer); //GL_RGBA8 was GL_RGB8A
return textureID;
}
You are now able to bind the returned textureID with glBindTexture(GL_TEXTURE_2D, textureID); if you need the texture.
This way you only have to split the BufferedImage in the desired parts.
I recommend reading this: LWJGL Textures and Strings
I followed a tutorial for reading a picture and creating a texture out of it, however, it shows up flipped upside down when rendered. The image is power of two.
Main class
public class Main {
public static void main(String args[]) throws IOException{
Main quadExample = new Main();
quadExample.start();
}
public void start() throws IOException {
try {
Display.setDisplayMode(new DisplayMode(1280,720));
Display.create();
} catch (LWJGLException e) {
e.printStackTrace();
System.exit(0);
}
// init OpenGL
GL11.glMatrixMode(GL11.GL_PROJECTION);
GL11.glLoadIdentity();
GL11.glOrtho(0, 1280, 0, 720, -1, 1);
GL11.glMatrixMode(GL11.GL_MODELVIEW);
GL11.glClearColor(0, 1, 0, 0);
GL11.glEnable(GL11.GL_TEXTURE_2D);
BufferedImage image = TextureLoader.loadImage("C:\\test.png");
final int textureID = TextureLoader.loadTexture(image);
while (!Display.isCloseRequested()) {
GL11.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MIN_FILTER, GL11.GL_NEAREST);
GL11.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MAG_FILTER, GL11.GL_NEAREST);
GL11.glBindTexture(GL11.GL_TEXTURE_2D, textureID);
GL11.glBegin(GL11.GL_QUADS);
GL11.glTexCoord2f(0, 0);
GL11.glVertex2f(0, 0);
GL11.glTexCoord2f(1, 0);
GL11.glVertex2f(256, 0);
GL11.glTexCoord2f(1, 1);
GL11.glVertex2f(256, 256);
GL11.glTexCoord2f(0, 1);
GL11.glVertex2f(0, 256);
GL11.glEnd();
Display.update();
}
Display.destroy();
}
}
Texture Loader
public class TextureLoader {
private static final int BYTES_PER_PIXEL = 4;
public static int loadTexture(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);
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));
buffer.put((byte) ((pixel >> 8) & 0xFF));
buffer.put((byte) (pixel & 0xFF));
buffer.put((byte) ((pixel >> 24) & 0xFF));
}
}
buffer.flip();
int textureID = glGenTextures();
glBindTexture(GL_TEXTURE_2D, textureID);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL12.GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL12.GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, image.getWidth(),
image.getHeight(), 0, GL_RGBA, GL_UNSIGNED_BYTE, buffer);
return textureID;
}
public static BufferedImage loadImage(String location) {
try {
return ImageIO.read(new File(location));
} catch (IOException e) {
System.out.println(Errors.IOException);
e.printStackTrace();
}
return null;
}
Is there something wrong with the code or do I have to flip the image before creating the texture?
Most image formats store the data top to bottom. Unless you reshuffle the data while loading the image, this is also the sequence in memory after reading the image.
When you create an OpenGL texture from the loaded image, this memory order is maintained unless you explicitly change the order. So the order in texture memory is still top to bottom.
OpenGL does not really have an image/texture orientation. But when you use texture coordinates, they address the texture in the order it's stored in memory. This means for the two extreme values of the t-coordinate:
t = 0.0 corresponds to the start of the image in memory, which is the top edge of the image.
t = 1.0 corresponds to the end of the image in memory, which is the bottom edge of the image.
Now, looking at your draw calls:
GL11.glTexCoord2f(0, 0);
GL11.glVertex2f(0, 0);
GL11.glTexCoord2f(1, 0);
GL11.glVertex2f(256, 0);
GL11.glTexCoord2f(1, 1);
GL11.glVertex2f(256, 256);
GL11.glTexCoord2f(0, 1);
GL11.glVertex2f(0, 256);
In the default OpenGL coordinate system, the y-coordinate goes bottom to top. So the first two vertices are the bottom vertices of the quad (since they have the smaller y-coordinate), the remaining two are the top two vertices.
Since you used t = 0.0 for the first two vertices, which are at the bottom of the quad, and t = 0.0 corresponds to the top of the image, the top of the image is at the bottom of the quad. Vice versa, you use t = 1.0 for the second two vertices, which are at the top of the quad, and t = 1.0 corresponds to the bottom of the image. Therefore, your image appears upside down.
By far the easiest way to fix this is to change the texture coordinates. Use t = 1.0 for the bottom two vertices, and t = 0.0 for the top two vertices, and the image orientation now matches the orientation of the quad on the screen:
GL11.glTexCoord2f(0.0f, 1.0f);
GL11.glVertex2f(0.0f, 0.0f);
GL11.glTexCoord2f(1.0f, 1.0f);
GL11.glVertex2f(256.0f, 0.0f);
GL11.glTexCoord2f(1.0f, 0.0f);
GL11.glVertex2f(256.0f, 256.0f);
GL11.glTexCoord2f(0.0f, 0.0f);
GL11.glVertex2f(0.0f, 256.0f);
Another option is that you flip the image while reading it in, for example by changing the order of your for loop from:
for (int y = 0; y < image.getHeight(); y++) {
to:
for (int y = image.getHeight() - 1; y >= 0; y--) {
But it's very common to have images in top-down order in memory, and you often don't have control over it if you're using system libraries/frameworks for reading them. So using the texture coordinates to render them in the desired direction is a frequently used approach, and IMHO preferable over shuffling the data around.
I have fix the problem and following is how I did it,
The binding code in RenderEngine:
public int bindTexture(String location)
{
BufferedImage texture;
File il = new File(location);
if(textureMap.containsKey(location))
{
glBindTexture(GL_TEXTURE_2D, textureMap.get(location));
return textureMap.get(location);
}
try
{
texture = ImageIO.read(il);
}
catch(Exception e)
{
texture = missingTexture;
}
try
{
int i = glGenTextures();
ByteBuffer buffer = BufferUtils.createByteBuffer(texture.getWidth() * texture.getHeight() * 4);
Decoder.decodePNGFileToBuffer(buffer, texture);
glBindTexture(GL_TEXTURE_2D, i);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, texture.getWidth(), texture.getHeight(), 0, GL_RGBA, GL_UNSIGNED_BYTE, buffer);
textureMap.put(location, i);
return i;
}
catch(Exception e)
{
e.printStackTrace();
}
return 0;
}
And the PNG decoder method:
public static void decodePNGFileToBuffer(ByteBuffer buffer, BufferedImage image)
{
int[] pixels = new int[image.getWidth() * image.getHeight()];
image.getRGB(0, 0, image.getWidth(), image.getHeight(), pixels, 0, image.getWidth());
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));
buffer.put((byte) ((pixel >> 8) & 0xFF));
buffer.put((byte) (pixel & 0xFF));
buffer.put((byte) ((pixel >> 24) & 0xFF));
}
}
buffer.flip();
}
I hope this helps anybody with the same problem
P.S. textureMap is just a HashMap with String as the key and a Integer as the value
You've got the order completely wrong. You need to:
Generate a texture name/ID with glGenTextures – store that ID in a variable
Bind that ID using glBindTexture
any only then you can upload the data with glTexImage
In your drawing code you're calling the whole texture load, which is inefficient, also you're recreating a new texture name each time. Use a map to map texture filenames to an ID, and only if no ID has been assigned yet Gen/Bind/TexImage the texture. Otherwise, just Bind it.