LWJGL Forum

Programming => Lightweight Java Gaming Library => Topic started by: Super_Llama on June 08, 2010, 23:47:10

Title: Sending Textures to Fragment Shaders - solved
Post by: Super_Llama on June 08, 2010, 23:47:10
I can't seem to find any tutorials for setting a uniform sampler2D inside a fragment shader program. I've done it with floats, and I've thoroughly searched ARBShaderObjects and GL11 for any relevant functions, but I haven't had any luck. The GLSL is easy, but I can't figure out how to create a texture, let alone apply it to the shader.

Any explanations would be great! I'm trying to make a 2D pixel-lit world with deformable terrain.
Title: Re: Sending Textures to Fragment Shaders
Post by: Matthias on June 09, 2010, 06:33:39
You need to use glUniform1i and specify the texture unit (not the texture ID) starting from 0.
glActiveTexture(GL_TEXTURE0 + unit);
glBindTexture(textureID);
glUniform1i(shaderUniformID, unit);

           
Title: Re: Sending Textures to Fragment Shaders
Post by: Super_Llama on June 09, 2010, 14:39:41
Thanks, that explains part of it-- but I still need to know how to create a texture unit  :-\
Title: Re: Sending Textures to Fragment Shaders
Post by: Ciardhubh on June 09, 2010, 15:46:01
Quote from: Super_Llama on June 09, 2010, 14:39:41
Thanks, that explains part of it-- but I still need to know how to create a texture unit  :-\

You don't create texture units, you just bind textures to the unit you want.
1. check how many the driver supports (http://www.opengl.org/sdk/docs/man/xhtml/glActiveTexture.xml, explanation under parameter description)
2. set active unit: glActiveTexture(GL_TEXTURE1)
3. bind texture
4. assign sampler2D variable a value of 1

Search google for "opengl multi-texturing" for more.
Title: Re: Sending Textures to Fragment Shaders
Post by: Super_Llama on June 09, 2010, 16:02:36
The question is, how do you GET a texture to bind? That's what I meant by create texture unit-- I assumed it was similar to creating a shader program. I don't know how the java program stores the texture-- uploading it to the GPU once it's been created makes sense now though.

I need to know:
How to make an empty texture object in java and fill it with data
How to send this data to the fragment shader for use
How to modify the data once it's been sent, either inside the shader or by re-uploading chunks of data that have been modified
How to send more than one texture to the fragment shader at once (I'm planning on a color map for rendering, a stress map for cave-ins, a light map for enhancements, and a material map for various other calculations)

EDIT:
Quote from: Ciardhubh on June 09, 2010, 15:46:01
Search google for "opengl multi-texturing" for more.
Whoops, didn't see that. I'm doing that now, hopefully there will be enough information there to do what I want. Thanks :)

EDIT2:
Unfortunately that didn't help :|
All the tutorials I can find are for fixed-function multitexturing. I just need to know how to make a texture and send it to my fragment shader for sampling. Multiple texture coordinates aren't necessary since all the textures correspond to one another.
Title: Re: Sending Textures to Fragment Shaders
Post by: Ciardhubh on June 09, 2010, 22:00:25
Works the same way you do it for fixed-function:
1. glGenTextures, to create tex objects
2. set active unit, to set the tex unit you want to bind to
3. bind, set texture you want to modify
4. set properties and glTexImage, to upload data to GPU
5. set sampler uniform, to map your uniform to that unit
6. use in GLSL

There's (almost) no difference between fixed-function and glsl in the way you set up textures. If you don't know how to do that I suggest reading general OpenGL tutorials (Google has dozens of them, Nehe's are quite popular) or read a more thorough book like the OpenGL Programming Guide (examples from that book in LWJGL are here: http://ciardhubh.de/node/13) or use an engine that does this for you (e.g. Slick, jME, jPCT, Ardor3D).

You modify textures by uploading different data via glTexImage. Textures are read-only in shaders.

You can use more textures by binding them to different texture units with glActiveTex...

If by "how the java program stores the texture" you mean loading image data from a file, then you need a loader for that image format. Existing engines like Slick or jME have those. That's not part of LWJGL.
Title: Re: Sending Textures to Fragment Shaders
Post by: Super_Llama on June 10, 2010, 03:20:16
Quote from: Ciardhubh on June 09, 2010, 22:00:25If you don't know how to do that I suggest reading general OpenGL tutorials (Google has dozens of them, Nehe's are quite popular) or read a more thorough book like the OpenGL Programming Guide (examples from that book in LWJGL are here: http://ciardhubh.de/node/13)
Thanks, I'll look into those.
Quote from: Ciardhubh on June 09, 2010, 22:00:25
You modify textures by uploading different data via glTexImage. Textures are read-only in shaders.
Is there any way to upload a part of a large texture, or the entire texture relatively fast with no need to attempt optimization?
Quote from: Ciardhubh on June 09, 2010, 22:00:25
If by "how the java program stores the texture" you mean loading image data from a file, then you need a loader for that image format. Existing engines like Slick or jME have those.
Like I said in the last post, I'm not (for this specific shader at least) loading it from a file, but rather generating a starting image and is affected by various game events. I just need to know what the array of bytes that I upload to the GPU needs to look like :)
Title: Re: Sending Textures to Fragment Shaders
Post by: Ciardhubh on June 10, 2010, 06:39:11
Quote from: Super_Llama on June 10, 2010, 03:20:16
Is there any way to upload a part of a large texture, or the entire texture relatively fast with no need to attempt optimization?
Something like http://www.opengl.org/sdk/docs/man/xhtml/glTexSubImage2D.xml ?

Quote from: Super_Llama on June 10, 2010, 03:20:16
I just need to know what the array of bytes that I upload to the GPU needs to look like :)

Image data is an array of bytes. OpenGL pixels starts at the bottom left corner of the image (as opposed to top left as in Java's BufferedImage for example). Pixels components you pass to OpenGL are usually defined as RGB or RGBA tuples (or any of the format types here: http://www.opengl.org/sdk/docs/man/xhtml/glTexImage2D.xml). Whereas one component is for example of type  GL_UNSIGNED_BYTE (a value from 0 to 255) or 4 bytes for GL_FLOAT (0.0 to 1.0 IIRC).

LWJGL requires this array to be wrapped in a direct Buffer; use BufferUtils to create one.
Title: Re: Sending Textures to Fragment Shaders
Post by: Super_Llama on June 10, 2010, 15:55:50
Quote from: Ciardhubh on June 10, 2010, 06:39:11
Something like http://www.opengl.org/sdk/docs/man/xhtml/glTexSubImage2D.xml ?
That's exactly the sort of thing I was looking for ;D
Quote from: Ciardhubh on June 10, 2010, 06:39:11
Image data is an array of bytes. OpenGL pixels starts at the bottom left corner of the image (as opposed to top left as in Java's BufferedImage for example). Pixels components you pass to OpenGL are usually defined as RGB or RGBA tuples (or any of the format types here: http://www.opengl.org/sdk/docs/man/xhtml/glTexImage2D.xml). Whereas one component is for example of type  GL_UNSIGNED_BYTE (a value from 0 to 255) or 4 bytes for GL_FLOAT (0.0 to 1.0 IIRC).

LWJGL requires this array to be wrapped in a direct Buffer; use BufferUtils to create one.
Okay, that makes sense. Thanks :)

Let's see if I've got this right:

Use glGenTextures to create a texture object
Set the active unit and bind it
Set the image stuff like RGBA/GL_UNSIGNED_BYTE (not sure what function does this)
Use glTexImage or glTexSubImage2D to upload a texture or parts of it to the GPU (both contained in a ByteBuffer, read from left to right, bottom to top, 4 bytes per pixel)

Then, when rendering,

Set the active shader to the one you want
Activate/bind the texture to one of the GL_TEXTURE# registers
Set the uniform sampler2D to the texture unit (an int)

...and the GLSL does the rest?

I think I get it now, thanks for all the help. All that I need to know now is which function sets the image format for a texture unit before sending the data-- but I can probably find this by poking through the code completion menu and/or google.

So it looks like uniform sampler2D's are actually a sort of pointer in the GPU? Though they don't point to a memory location, they're still ints that point somewhere and have no other use in themselves. As long as I think about it that way, it makes perfect sense :)
Title: Re: Sending Textures to Fragment Shaders
Post by: Ciardhubh on June 10, 2010, 17:22:08
Sounds about right. Don't forget to set tex coordinates and depending on your texture glTexParameter might be relevant too: http://www.opengl.org/sdk/docs/man/xhtml/glTexParameter.xml

Quote from: Super_Llama on June 10, 2010, 15:55:50
Set the image stuff like RGBA/GL_UNSIGNED_BYTE (not sure what function does this)

glTexImage... does that, e.g.:
GL11.glTexImage2D(GL11.GL_TEXTURE_2D, 0, GL11.GL_RGBA, texWidth, texHeight, 0, GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE, imageBuffer);

Title: Re: Sending Textures to Fragment Shaders
Post by: Super_Llama on June 10, 2010, 17:29:45
Having difficulty with the texture units...
GL11.glBindTexture has a 'target' parameter, apparently deciding whether to use a 1D, 2D, or Cube texture. GL13.glActiveTexture is apparently how I set the texture unit. I've been using GL_TEXTURE_2D for BindTexture and GL_TEXTURE1 for ActiveTexture, and sending '1' to the shader. I'm not sure what I'm doing wrong, but for some reason the shader is getting an empty texture (texture2D is always returning 0,0,0,0). I'm pretty sure my ByteByffer is right-- I'm creating it, putting values, and flipping it, so I'm thinking it's probably a problem with my understanding of texture units and texture targets.

From what I can see, it looks like each texture unit can hold one of each kind of texture, and that's what the targets are, but perhaps there's some incompatibility between the functions, using different models of texture storage?

The texture coordinates are being sent properly so that's not the problem.
Title: Re: Sending Textures to Fragment Shaders
Post by: Ciardhubh on June 10, 2010, 18:05:33
Can you post your texture-relevant LWJGL and shader code?

Also shader units start at 0. Just saying so you don't waste one.

Try using the lwjgl-debug.jar instead of lwjgl.jar to see if any of the calls cause an OpenGL error.
Title: Re: Sending Textures to Fragment Shaders
Post by: Super_Llama on June 10, 2010, 18:26:31
I used 0 initially but it didn't work so I tried 1. I'll switch back to 0 since that wasn't the problem.
This is the code that generates the texture (reddish noise):

       public void MakeTexture()
       {
           texrand = GL11.glGenTextures();
           GL13.glActiveTexture(GL13.GL_TEXTURE0);
           GL11.glBindTexture(GL11.GL_TEXTURE_2D, texrand);
           ByteBuffer bb = BufferUtils.createByteBuffer(64*64*4);
           for (int i=0; i<4096; i++) {
               bb.put((byte)(155+Math.random()*100));
               bb.put((byte)(Math.random()*64));
               bb.put((byte)(Math.random()*128));
               bb.put((byte)255);
           }
           bb.flip();
           GL11.glTexImage2D(GL11.GL_TEXTURE_2D, 0, 4, 64, 64, 0,
                             GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE, bb);
       }

This is the code that binds it:

         GL13.glActiveTexture(GL13.GL_TEXTURE0);
         GL11.glBindTexture(GL11.GL_TEXTURE_2D, texrand);
         int loc = ARBShaderObjects.glGetUniformLocationARB(drawshad, "tex");
         ARBShaderObjects.glUniform1iARB(loc, 0);

And this is my test fragment shader:

uniform sampler2D tex;
uniform float sa; //sine of the current phase- just a test variable to make sure the two components can communicate

void main(void)
{
    vec4 color = texture2D(tex,gl_TexCoord[0].st);
    color.x *= sa; //multiply the red component by the animation variable
    color.w = 1; //make sure the alpha is 100%
    gl_FragColor = color;
}


EDIT: I changed to lwjgl-debug.jar and I'm getting spammed with an invalid enum error when setting the matrix. Not sure why, because that function worked just fine...
Title: Re: Sending Textures to Fragment Shaders
Post by: Ciardhubh on June 10, 2010, 19:08:37
GL11.glTexImage2D(GL11.GL_TEXTURE_2D, 0, GL11.GL_RGBA, 64, 64, 0, GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE, bb);

If you don't need the alpha channel you can also drop it and only use GL11.GL_RGB to save memory.
Title: Re: Sending Textures to Fragment Shaders
Post by: Super_Llama on June 10, 2010, 19:12:26
Quote from: Ciardhubh on June 10, 2010, 19:08:37
GL11.glTexImage2D(GL11.GL_TEXTURE_2D, 0, GL11.GL_RGBA, 64, 64, 0, GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE, bb);

If you don't need the alpha channel you can also drop it and only use GL11.GL_RGB to save memory.

Didn't help, same problem. I read somewhere that passing '4' is the same as passing 'RGBA', but even after correcting it like you said, it still doesn't send the texture.

EDIT:
I added

if (color.y == 0) {
    color = vec4(1,0.8,0.5,1);
}

in the glsl, and the quad showed up-- so it looks like it's sending a ton of black pixels  ???
Title: Re: Sending Textures to Fragment Shaders
Post by: Ciardhubh on June 10, 2010, 19:42:09
Yea ... that 4 was probably ok *cough*

Do you have a Vertex shader (not sure if one is mandatory if you have a fragment program)? Do you set gl_TexCoord[0] = gl_MultiTexCoord0; in it to pass it to the fragment shader?

Do you still get exceptions with the debug build? Can't really see something right now - guess it's late - without running it myself.
Title: Re: Sending Textures to Fragment Shaders
Post by: Super_Llama on June 10, 2010, 20:44:30
The texture coordinates are being accessed perfectly, I can pass UVW as RGB and it renders as expected. I'm not using a vertex shader since the default one (or lack thereof) is sufficient for what I"m trying to do. The only problem is that it's not sending the buffer. I might try using a FloatBuffer instead. And yes, the debug build is still pumping out exceptions regarding MatrixMode, which is weird because there's nothing wrong with the matrices.
Title: Re: Sending Textures to Fragment Shaders
Post by: Ciardhubh on June 10, 2010, 21:22:01
Well, an exception means something's wrong. I'd say, find the call responsible and check the OpenGL spec (http://www.opengl.org/sdk/docs/man/) for cases in which this error is thrown.
Title: Re: Sending Textures to Fragment Shaders
Post by: Super_Llama on June 11, 2010, 00:29:43
I'm fixing them one by one-- seems like all of them are in the matrix code I copy-pasted from a sample, rofl. I'll post back if I find the culprit, or if the error river runs dry...

EDIT: I found it!
Exception in thread "AWT-EventQueue-2" org.lwjgl.opengl.OpenGLException: Invalid operation (1282)
at org.lwjgl.opengl.Util.checkGLError(Util.java:56)
at org.lwjgl.opengl.ARBShaderObjects.glUniform1iARB(ARBShaderObjects.java:231)


Unfortunately I have no idea how to fix it, since I'm doing exactly what Matthias said on page 1... it seems to think that a Uniform1i can't be used to set a sampler2D.

EDIT 2:
Quote
GL_INVALID_OPERATION is generated if a   sampler is loaded using a command other than glUniform1i and glUniform1iv.
I wonder if glUniform1iARB is different than glUniform1i...? I guess I'll try iv...
Title: Re: Sending Textures to Fragment Shaders
Post by: Ciardhubh on June 11, 2010, 06:58:30
Invalid operation is also thrown if:
GL_INVALID_OPERATION is generated if glUniform is executed between the execution of glBegin and the corresponding execution of glEnd. (called between begin and end?)
GL_INVALID_OPERATION is generated if there is no current program object. (no call to glUseProgram?)
GL_INVALID_OPERATION is generated if location is an invalid uniform location for the current program object and location is not equal to -1. (did glGetUniformLocation run ok?)
Title: Re: Sending Textures to Fragment Shaders
Post by: Super_Llama on June 11, 2010, 13:49:14
Quote from: Ciardhubh on June 11, 2010, 06:58:30
GL_INVALID_OPERATION is generated if there is no current program object. (no call to glUseProgram?)
That's what it was. I reordered the calls and the exception went away, but the quad still renders without a texture >_<

EDIT:
The river of errors has run dry, and there's still no solution... linking to debug makes absolutely no difference now, and the quad still shows up black. I tried using a FloatBuffer instead of a ByteBuffer and it didn't help.

EDIT 2: Still waiting for help, changed title to make sure people notice :-\
Title: Re: Sending Textures to Fragment Shaders - unsolved
Post by: Rene on June 13, 2010, 14:55:41
The default OpenGL minification filter uses mipmaps. You can easily generate mipmaps with

EXTFramebufferObject.glGenerateMipmapEXT(GL11.GL_TEXTURE_2D);


Or change the minification filter, depending on your needs.
Title: Re: Sending Textures to Fragment Shaders - unsolved
Post by: Super_Llama on June 13, 2010, 16:57:47
I had a hunch that it had something to do with mipmaps, but I didn't know where to start. Thanks for the advice, I really hope this works!

EDIT: YES!!! IT WORKED! Thanks a lot, I was just about ready to give up. I'd +rep if the forum had such a feature :P