Texture Masking (it's driving me crazy)

Started by heishe, April 12, 2011, 06:58:00

Previous topic - Next topic

heishe

Hi.

How can I mask textures (meaning that when a texture contains a certain color, that color is completely transparent) in LWJGL without manually creating a black/white texture for each texture that I'm going to use?

LWJGL is driving me completely crazy in that regard. I'm using slicks TextureLoader and Texture classes to load a PNG file, but since I have no way of manipulating the actual texture data inside those classes (texture.getTextureData() only returns copies of the values and additionally the data returned is always 65543 something entries long, which is ridiculous for a 100x100 texture), I can't manually set the alpha of pixels to 0.0f which match the color that is to be masked.

Since I don't know how to load PNGs myself, I'm now trying to create the texture manually a second time after loading it with TextureLoader, but I can't even get normal texturing to work manually, lol!:

try
		{
			m_texture = ResourceManager.getTexture(file_location);
			
			//safe the texture ID given by glGenTextures()
			m_textureID = GL11.glGenTextures();
			//bind it
			GL11.glBindTexture(GL11.GL_TEXTURE_2D, m_textureID);
			
			//get pixel data out of m_texture
			byte[] pixels = m_texture.getTextureData();
			
			System.out.println(m_texture.getTextureData().length);
			ByteBuffer bb = ByteBuffer.allocate(pixels.length);
			bb.put(pixels);
			IntBuffer ib = bb.asIntBuffer();
			GL11.glTexImage2D(GL11.GL_TEXTURE_2D, 0, 4, m_texture.getTextureWidth(), 
			m_texture.getTextureHeight(), 0, GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE, ib);
			
			GL11.glTexParameteri(GL11.GL_TEXTURE_2D,GL11.GL_TEXTURE_MIN_FILTER,GL11.GL_LINEAR);	
			GL11.glTexParameteri(GL11.GL_TEXTURE_2D,GL11.GL_TEXTURE_MAG_FILTER,GL11.GL_LINEAR);
			
		}


This block always throws an exception which says that the remaining buffer contains 0 elements.

This is my sprite drawing function btw, it works flawlessly if I just m_texture.bind() instead of manually binding my texture:

public void draw()
	{
		GL11.glPushMatrix();
		GL11.glBindTexture(GL11.GL_TEXTURE_2D, m_textureID);
		GL11.glTranslatef(m_position.x+(m_size.x/2.0f), m_position.y+(m_size.y/2.0f), 0.0f);
		
		GL11.glRotatef(m_angle, 0.0f, 0.0f,1.0f);
		GL11.glBegin(GL11.GL_QUADS);
		
		GL11.glTexCoord2f(0.0f,0.0f); 
		GL11.glVertex3f(m_spriterect.left,m_spriterect.bottom,0.0f);
		GL11.glTexCoord2f(m_tex_coord_value,0.0f);
		GL11.glVertex3f(m_spriterect.right,m_spriterect.bottom,0.0f);
		GL11.glTexCoord2f(m_tex_coord_value,m_tex_coord_value); 
		GL11.glVertex3f(m_spriterect.right,m_spriterect.top,0.0f);
		GL11.glTexCoord2f(0.0f,m_tex_coord_value); 
		GL11.glVertex3f(m_spriterect.left,m_spriterect.top,0.0f);
		
		GL11.glEnd();
		GL11.glPopMatrix();
	}


I'm coming from C++ and wanted to code a little 2D engine in Java, but it's driving me nuts.

Can I even create a new manual texture this way? Or how could I go about "auto-texture-masking" while still using Texture and TextureLoader?

Fool Running

Ideally, what you should do is create the PNG texture in 32 bit color (with an alpha channel) and make the alpha for those pixels zero. It's far more efficient and is supported by the texture loader and OpenGL.

Just make sure you have blending turned on. ;D
Programmers will, one day, rule the world... and the world won't notice until its too late.Just testing the marquee option ;D

heishe

I've got manual texturing running now, blending doesn't work correctly though. Instead of blending out the masked color completely, it just does normal blending (blends fore and background together)

This is my current code that changes the alpha values:
public void createMaskFromColor(int r,int g,int b)
	{
		ByteBuffer masked_buffer = ByteBuffer.allocateDirect(m_texture_data.capacity());
	
		for(int i=0;i<m_texture_data.capacity();i+=4)
		{
			int rr = m_texture_data.get(i);
			int gg = m_texture_data.get(i+1);
			int bb = m_texture_data.get(i+2);
			int alpha = m_texture_data.get(i+3);
			if(r == rr && g == gg && b == bb)
			{
				alpha = 0;
			}
			
			masked_buffer.put((byte)rr);
			masked_buffer.put((byte)gg);
			masked_buffer.put((byte)bb);
			masked_buffer.put((byte)alpha);
		}
		masked_buffer.rewind();
		GL11.glBindTexture(GL11.GL_TEXTURE_2D, m_textureID);
		
		GL11.glTexImage2D(GL11.GL_TEXTURE_2D, 0,4, (int)m_size.x , (int)m_size.y, 0,
				GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE,masked_buffer);
		
		m_texture_data = masked_buffer;
		
		GL11.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MIN_FILTER, GL11.GL_LINEAR);
		GL11.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MAG_FILTER, GL11.GL_LINEAR);
	
	}


Basically what it does is assemble a new texture with new alpha values.

I've already tried palying around with the glBlendFunc() functions, but they all either produce the same result or I get a completely black screen.

Quote from: Fool Running on April 12, 2011, 12:30:39
Ideally, what you should do is create the PNG texture in 32 bit color (with an alpha channel) and make the alpha for those pixels zero. It's far more efficient and is supported by the texture loader and OpenGL.

Just make sure you have blending turned on. ;D

No, I don't want to do anything with the png manually in an editor or something. I want the user of my little library to be able to specificy the masked color in the code and have it blended completely away during rendering.

avm1979

Quote from: heishe on April 12, 2011, 06:58:00
additionally the data returned is always 65543 something entries long, which is ridiculous for a 100x100 texture)

The texture size gets increased to the closest power of two (so, 128x128), and there's 4 bytes per pixel (r,g,b,a), so you have:

128x128x4 = 65536

Not ridiculous at all, but if you want to be more efficient and avoid wasted memory, you can pack multiple textures into an atlas.

Quote from: heishe on April 12, 2011, 06:58:00
Since I don't know how to load PNGs myself

BufferedImage image = ImageIO.load("blah.png");


:)

jediTofu

You probably want something like this in your opengl initialization:

glAlphaFunc(GL_GREATER,0.1f) ;
glEnable(GL_ALPHA_TEST) ;


*EDIT*  Read your last post.  You would have to do it the way you are doing, or cycle through every color in the image and set the alpha (which isn't that bad if using a VBO or FBO or something).
cool story, bro

heishe

argh, I'm an idiot. I forgot to cast the arguments of the createMaskFromColor to (byte) while comparing them to the colors of the pixels. It works perfectly now.

jediTofu: The performance doesn't really matter in that case, unless it would be ridiculously slow. But it's only (width*height*4) runs through the loop, which isn't bad at all since it's only run once right when the user creates the mask (it's up to the programmer to not do this 50 times per frame :P ).

Matthias

If you want to load the PNG yourself use TWL's PNGDecoder - it can output the data in different formats and is very fast.

avm1979

Quote from: Matthias on April 13, 2011, 06:15:00
If you want to load the PNG yourself use TWL's PNGDecoder - it can output the data in different formats and is very fast.

I've been meaning to ask about it - it only handles power of two sized png's, right?  I tried switching to use it a while back and ran into that, though I could've been doing something wrong.