1
votes

I'm currently trying to draw simple meshes using different textures (using C# and OpenTK). I read a lot about TextureUnit and bindings, and that's my current implementation (not working as expected) :

private void ApplyOpaquePass()
{
    GL.UseProgram(this.shaderProgram);
    GL.CullFace(CullFaceMode.Back);

    while (this.opaqueNodes.Count > 0)
        Draw(this.opaqueNodes.Pop());

    GL.UseProgram(0);
}

And my draw method :

private void Draw(Assets.Model.Geoset geoset)
{
    GL.ActiveTexture(TextureUnit.Texture1);
    GL.BindTexture(TextureTarget.Texture2D, geoset.TextureId /*buffer id returned by GL.GenTextures*/ );
    GL.Uniform1(GL.GetUniformLocation(this.shaderProgram, "Texture1"), 1 /*see note below*/ );

    //Note: if I'm correct, it should be 1 when using TextureUnit.Texture1
    //      (2 for Texture2...), note that doesn't seem to work since no
    //      texture texture at all is sent to the shader, however a texture
    //      is shown when specifying any other number (0, 2, 3...)

    // Draw vertices & indices buffers...
}

And my shader code (that shouldn't be the problem since uv mapping is ok):

uniform sampler2D Texture1;

void main(void)
{
    gl_FragColor = texture2D(Texture1, gl_TexCoord[0].st);
}


What's the problem :

  • Since geoset.TextureId can vary from one geoset to another, I'm expecting different texture to be sent to the shader.

  • Instead, always the same texture is applied to all objects (geosets).


Ideas :

  • Using different TextureUnit for each textures (working well), but what happens if we have 2000 different textures? If my understanding is right, we must use multiple TextureUnit only if we want to use multiple texture at the same time in the shader.

  • I first thought that uniforms couldn't be changed once defined, but a test with a boolean uniform told me that it was actually possible.

    private void Draw(Assets.Model.Geoset geoset)
    {
        GL.ActiveTexture(TextureUnit.Texture1);
        GL.BindTexture(TextureTarget.Texture2D, geoset.TextureId);
        GL.Uniform1(GL.GetUniformLocation(this.shaderProgram, "Texture1"), 1 );
    
        //added line...
        GL.Uniform1(GL.GetUniformLocation(this.shaderProgram, "UseBaseColor"), (geoset.Material.FilterMode == Assets.Model.Material.FilterType.Blend) ? 1: 0);
    
        // Draw vertices & indices buffers...
    }
    

    Shader code:

    uniform sampler2D Texture1;
    uniform bool UseBaseColor;
    
    void main(void)
    {
        gl_FragColor = texture2D(Texture1, gl_TexCoord[0].st);
    
        if (UseBaseColor)
            gl_FragColor = mix(vec4(0,1,1,1), gl_FragColor , gl_FragColor .a);
    }
    

    This code works great, drawing some geoset with a base color instead of transparency, that (should ?) prove that uniforms can be changed here. Why this isn't working with my textures ?

  • Should I use a different shader program per geoset ?



Thanks in advance for your answers :)

Regards,
Bruce

EDIT: that's how I generate textures in the renderer:

override public uint GenTexture(Bitmap bmp)
{
    uint texture;
    GL.GenTextures(1, out texture);

        //I disabled this line because I now bind the texture before drawing a geoset
        //Anyway, uncommenting this line doesn't show a better result
        //GL.BindTexture(TextureTarget.Texture2D, texture);

    System.Drawing.Imaging.BitmapData data = bmp.LockBits(new Rectangle(0, 0, bmp.Width, bmp.Height), System.Drawing.Imaging.ImageLockMode.ReadOnly, System.Drawing.Imaging.PixelFormat.Format32bppArgb);
    GL.TexImage2D(TextureTarget.Texture2D, 0, PixelInternalFormat.Rgba, data.Width, data.Height, 0,
        OpenTK.Graphics.OpenGL.PixelFormat.Bgra, PixelType.UnsignedByte, data.Scan0);
    bmp.UnlockBits(data);

    //temp settings
    GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMinFilter, (int)TextureMinFilter.Linear);
    GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMagFilter, (int)TextureMagFilter.Linear);

    return texture;
}
2
The code you've posted seems to make sense, as do your assumptions that it is possible to change a uniform between draw calls and that is is ok to reuse a texture unit for different textures as long as you don't need more than one for a given draw call / shader. Either I've missed something or the issue is somewhere else, possibly in the way your textures are created ?Nicolas Lefebvre
Yes; are you sure that geoset.TextureID is set correctly in Draw() ? EDIT in GenTexture, it's very important to bind the texture before calling texImage2d, or you'll overwrite the previously-bound texture instead of the new oneCalvin1602
If I break in the Draw method, I can see different values of geoset.TextureID (also material's data that was used to generated the texture is correct, ie filename...), but all result to render the same texture through the shader. (Note that textures was correctly drown with the standart glEnable(TEXTURE_2D) method) I uncommented the BindTexture call in the GenTexture method, but no change's noticeable.Profet
I'm worried about the first note in your code. Could you elaborate further? If 1 doesn't work and all other values do, what is the actual value you are passing in your code? And what are the textures you see when using other numbers?UncleZeiv
Originally, I followed a tutorial (opentk.com/node/2559) that uses one textureunit per texture and binds Uniform with the texture buffer Id, that's convenient since he's only creating 4 textures and can map TextureUnit1 to 4 for each (with correct order). But that's not practical when the texture count is undetermined. In my code, I don't need multiple TextureUnit since I don't render a multimaterial (at least not for the moment), so I only use textureunit's slot 1 (or zero if I keep the default TU).Profet

2 Answers

2
votes

I finally solved my problem ! All the answers perfected my understanding and lead me to the solution which lied on two major problems:

1) as Calvin1602 said, this is very important to bind a newly created texture before calling glTexImage2d.

2) also UncleZeiv rose my attention about the last GL.Uniform1's parameter. The OpenTK tutorial is very misleading because the guy pass the id of the texture object to the function, that happens to work here because the order of generation of the texture exactly matches the id of used TextureUnit. As I was unsure that my comprehension was exact, I wrongly changed this parameter back to the geoset.TextureId.

Thanks !

0
votes

You don't need multiple shader programs if the only thing you are changing is the texture. Also uniform locations are constant throughout the lifetime of a shader program, so there is no need to retrieve those each frame. However, you do need to rebind the texture each time you change it, and you will need to bind each distinct texture to a separate texture ID.

As a result, I would conclude that what you posted ought to work and so the problem is likely somewhere else in your code.

EDIT: After the updated version it should still work. However I am concerned about why the following line is commented out:

 //GL.BindTexture(TextureTarget.Texture2D, texture);

This should be in there. Otherwise you will keep over writing the same texture (which is ridiculous). You need to bind the texture before you initialize. Now it is entirely conceivable that something else is broken, but given what I see now this is the only error that jumps out at me.