ZTK's picture

Loading multiple textures and passing through to a shader

Hello everyone,

Many people seem to be confused as to how to load multiple textures. I'm writing this tutorial to give a concrete example of this. We will create a cube whose faces have four textures each. Each face is passed through to a fragment shader, that displays and tints each texture

The key point is this function, which will load textures

        private void CreateTexture(out int texture, Bitmap bitmap)
        {
            // load texture 
            GL.GenTextures(1, out texture);
 
            //Still required else TexImage2D will be applyed on the last bound texture
            GL.BindTexture(TextureTarget.Texture2D, texture);
 
            BitmapData data = bitmap.LockBits(new System.Drawing.Rectangle(0, 0, bitmap.Width, bitmap.Height),
            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);
 
            bitmap.UnlockBits(data);
            GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMinFilter, (int)TextureMinFilter.Linear);
        }

As well as this code which will bind the textures.

        private void BindTexture(ref int textureId, TextureUnit textureUnit, string UniformName)
        {
            GL.ActiveTexture(textureUnit);
            GL.BindTexture(TextureTarget.Texture2D, textureId);
            GL.Uniform1(GL.GetUniformLocation(shaderProgramHandle, UniformName), textureUnit - TextureUnit.Texture0);
        }

Note that we can't just pass textureUnit to GL.Uniform1. It has to be textureUnit - TextureUnit.Texture0. Another point of confusion on this function (that really confused me) is that the openTK tutorial has the texture object Id passed to GL.Uniform1. This is WRONG! You need to pass an int representing which textureUnit you are working with. So if you are on textureUnit.Texture4 (for example), then you'd pass "4" to GL.Uniform1. Alternatively, as Profet points out in the comments, you can do something like TextureUnit4 - TextureUnit0, which gives a result of "4".

These functions are called four times, like this:

            CreateTexture(out texture0, bitmap0);
            CreateTexture(out texture1, bitmap1);
            CreateTexture(out texture2, bitmap2);
            CreateTexture(out texture3, bitmap3);
 
            BindTexture(ref texture0, TextureUnit.Texture0, "MyTexture0");
            BindTexture(ref texture1, TextureUnit.Texture1, "MyTexture1");
            BindTexture(ref texture2, TextureUnit.Texture2, "MyTexture2");
            BindTexture(ref texture3, TextureUnit.Texture3, "MyTexture3");

One basic point for some readers, but one that I've seen some people confused about is this:
           GL.GenTextures(1, out texture);

The first param is a "1". It's always "1", because we are generating one texture. The "1" here has nothing to do with which texture we are generating.

Finally, here is our shader code. Note that it is passed four textures, and tints each one differently. It looks at the horizontal coordinate of the fragment, and applies the first texture if it is in the first quarter, second texture in the second quarter, etc.. The shader has access to all our textures at once.

#version 150
uniform sampler2D MyTexture0;
uniform sampler2D MyTexture1;
uniform sampler2D MyTexture2;
uniform sampler2D MyTexture3;
 
void main(void)
{
 if (gl_TexCoord[0].s < 0.25){
  gl_FragColor = texture2D( MyTexture0, gl_TexCoord[0].st );  
  gl_FragColor[1] = gl_FragColor[1] * 0.90;
 }
else if (gl_TexCoord[0].s < 0.5) {
  gl_FragColor = texture2D( MyTexture1, gl_TexCoord[0].st );  
  gl_FragColor[0] = gl_FragColor[0] * 0.90;
 }
else if (gl_TexCoord[0].s < 0.75) {
  gl_FragColor = texture2D( MyTexture2, gl_TexCoord[0].st );  
  gl_FragColor[2] = gl_FragColor[2] * 0.90;
 }
else {
  gl_FragColor = texture2D( MyTexture3, gl_TexCoord[0].st );  
 }
}

Here the full code. Create a VS2010 solution and create four images called pic1, pic2, pic3, and pic4 respectively as bitmaps.

using System;
using OpenTK;
using OpenTK.Graphics;
using OpenTK.Graphics.OpenGL;
using OpenTK.Audio;
using OpenTK.Audio.OpenAL;
using OpenTK.Input;
 
using System.Drawing;
using System.Drawing.Imaging;
using System.Diagnostics;
 
namespace StarterKit
{
 
    class Game : GameWindow
    {
        private Matrix4 modelviewMatrix, projectionMatrix;
        private Matrix4 rotationviewMatrix;
 
        int fragmentShaderHandle, shaderProgramHandle, texture0, texture1, texture2, texture3;
 
        const float rotation_speed = 18.0f;
 
        float angle;
 
        Bitmap bitmap0 = new Bitmap(@"..\..\img\pic1.bmp");
        Bitmap bitmap1 = new Bitmap(@"..\..\img\pic2.bmp");
        Bitmap bitmap2 = new Bitmap(@"..\..\img\pic3.bmp");
        Bitmap bitmap3 = new Bitmap(@"..\..\img\pic4.bmp");
 
        string fragmentShaderSource = @"
 
#version 150
uniform sampler2D MyTexture0;
uniform sampler2D MyTexture1;
uniform sampler2D MyTexture2;
uniform sampler2D MyTexture3;
 
void main(void)
{
 if (gl_TexCoord[0].s < 0.25){
  gl_FragColor = texture2D( MyTexture0, gl_TexCoord[0].st );  
  gl_FragColor[1] = gl_FragColor[1] * 0.90;
 }
else if (gl_TexCoord[0].s < 0.5) {
  gl_FragColor = texture2D( MyTexture1, gl_TexCoord[0].st );  
  gl_FragColor[0] = gl_FragColor[0] * 0.90;
 }
else if (gl_TexCoord[0].s < 0.75) {
  gl_FragColor = texture2D( MyTexture2, gl_TexCoord[0].st );  
  gl_FragColor[2] = gl_FragColor[2] * 0.90;
 }
else {
  gl_FragColor = texture2D( MyTexture3, gl_TexCoord[0].st );  
 }
}"
            ;
 
        void CreateShaders()
        {
 
            fragmentShaderHandle = GL.CreateShader(ShaderType.FragmentShader);
            GL.ShaderSource(fragmentShaderHandle, fragmentShaderSource);
            GL.CompileShader(fragmentShaderHandle);
            Debug.WriteLine(GL.GetShaderInfoLog(fragmentShaderHandle));
 
            // Create program
            shaderProgramHandle = GL.CreateProgram();
            GL.AttachShader(shaderProgramHandle, fragmentShaderHandle);
            GL.LinkProgram(shaderProgramHandle);
            Debug.WriteLine(GL.GetProgramInfoLog(shaderProgramHandle));
 
            GL.UseProgram(shaderProgramHandle);
 
 
            float aspectRatio = ClientSize.Width / (float)(ClientSize.Height);
            Matrix4.CreatePerspectiveFieldOfView((float)Math.PI / 4, aspectRatio, 1, 100, out projectionMatrix);
        }
 
        /// <summary>Creates a 800x600 window with the specified title.</summary>
        public Game()
            : base(800, 600, GraphicsMode.Default, "OpenTK Quick Start Sample")
        {
            VSync = VSyncMode.On;
        }
 
        /// <summary>Load resources here.</summary>
        /// <param name="e">Not used.</param>
        protected override void OnLoad(EventArgs e)
        {
            CreateShaders();
            base.OnLoad(e);
 
            GL.ClearColor(0.0f, 0.4f, 0.0f, 0.0f);
            GL.Enable(EnableCap.DepthTest);
            GL.Enable(EnableCap.Texture2D);
            GL.Hint(HintTarget.PerspectiveCorrectionHint, HintMode.Nicest);
 
            CreateTexture(out texture0, bitmap0);
            CreateTexture(out texture1, bitmap1);
            CreateTexture(out texture2, bitmap2);
            CreateTexture(out texture3, bitmap3);
 
            BindTexture(ref texture0, TextureUnit.Texture0, "MyTexture0");
            BindTexture(ref texture1, TextureUnit.Texture1, "MyTexture1");
            BindTexture(ref texture2, TextureUnit.Texture2, "MyTexture2");
            BindTexture(ref texture3, TextureUnit.Texture3, "MyTexture3");
 
            modelviewMatrix = Matrix4.LookAt(0, 4, 7, 0, 0, 0, 0, 1, 0);
 
            rotationviewMatrix = Matrix4.CreateRotationX((float)Math.PI / 100);// *Matrix4.CreateRotationX((float)Math.PI / 10000);
        }
 
        private void CreateTexture(out int texture, Bitmap bitmap)
        {
            // load texture 
            GL.GenTextures(1, out texture);
 
            //Still required else TexImage2D will be applyed on the last bound texture
            GL.BindTexture(TextureTarget.Texture2D, texture);
 
            BitmapData data = bitmap.LockBits(new System.Drawing.Rectangle(0, 0, bitmap.Width, bitmap.Height),
            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);
 
            bitmap.UnlockBits(data);
            GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMinFilter, (int)TextureMinFilter.Linear);
        }
 
        /// <summary>
        /// Called when your window is resized. Set your viewport here. It is also
        /// a good place to set up your projection matrix (which probably changes
        /// along when the aspect ratio of your window).
        /// </summary>
        /// <param name="e">Not used.</param>
 
        protected override void OnResize(EventArgs e)
        {
 
            base.OnResize(e);
            GL.Viewport(ClientRectangle.X, ClientRectangle.Y, ClientRectangle.Width, ClientRectangle.Height);
            projectionMatrix = Matrix4.CreatePerspectiveFieldOfView((float)Math.PI / 4, Width / (float)Height, 1.0f, 64.0f);
            GL.MatrixMode(MatrixMode.Projection);
            GL.LoadMatrix(ref projectionMatrix);
 
        }
 
        /// <summary>
        /// Called when it is time to setup the next frame. Add you game logic here.
        /// </summary>
        /// <param name="e">Contains timing information for framerate independent logic.</param>
 
        protected override void OnUpdateFrame(FrameEventArgs e)
        {
            base.OnUpdateFrame(e);
            if (Keyboard[Key.Escape])
                Exit();
        }
 
        /// <summary>
        /// Called when it is time to render the next frame. Add your rendering code here.
        /// </summary>
        /// <param name="e">Contains timing information.</param>
        protected override void OnRenderFrame(FrameEventArgs e)
        {
 
            base.OnRenderFrame(e);
            GL.Clear(ClearBufferMask.ColorBufferBit | ClearBufferMask.DepthBufferBit);
            angle += rotation_speed * (float)e.Time;
            float translateby = (float)Math.Sin(DateTime.Now.Second) * 0.05f;
            rotationviewMatrix = Matrix4.CreateTranslation(0f, 0f, translateby);
 
            GL.MatrixMode(MatrixMode.Modelview);
            GL.LoadMatrix(ref modelviewMatrix);
            GL.Rotate(angle, 0.0f, 1.0f, 0.0f);
 
 
            DrawCube();
            SwapBuffers();
        }
 
        private void BindTexture(ref int textureId, TextureUnit textureUnit, string UniformName)
        {
            GL.ActiveTexture(textureUnit);
            GL.BindTexture(TextureTarget.Texture2D, textureId);
            GL.Uniform1(GL.GetUniformLocation(shaderProgramHandle, UniformName), textureUnit - TextureUnit.Texture0);
        }
 
 
 
        private void DrawCube()
        {
            GL.Begin(BeginMode.Quads);
 
            GL.Color3(Color.Silver);
            GL.TexCoord2(0.0f, 1.0f); GL.Vertex3(-1.0f, -1.0f, -1.0f);
            GL.TexCoord2(1.0f, 1.0f); GL.Vertex3(-1.0f, 1.0f, -1.0f);
            GL.TexCoord2(1.0f, 0.0f); GL.Vertex3(1.0f, 1.0f, -1.0f);
            GL.TexCoord2(0.0f, 0.0f); GL.Vertex3(1.0f, -1.0f, -1.0f);
 
            GL.Color3(Color.Honeydew);
            GL.TexCoord2(0.0f, 1.0f); GL.Vertex3(-1.0f, -1.0f, -1.0f);
            GL.TexCoord2(1.0f, 1.0f); GL.Vertex3(1.0f, -1.0f, -1.0f);
            GL.TexCoord2(1.0f, 0.0f); GL.Vertex3(1.0f, -1.0f, 1.0f);
            GL.TexCoord2(0.0f, 0.0f); GL.Vertex3(-1.0f, -1.0f, 1.0f);
 
            GL.Color3(Color.Goldenrod);
            GL.TexCoord2(0.0f, 1.0f); GL.Vertex3(-1.0f, -1.0f, -1.0f);
            GL.TexCoord2(1.0f, 1.0f); GL.Vertex3(-1.0f, -1.0f, 1.0f);
            GL.TexCoord2(1.0f, 0.0f); GL.Vertex3(-1.0f, 1.0f, 1.0f);
            GL.TexCoord2(0.0f, 0.0f); GL.Vertex3(-1.0f, 1.0f, -1.0f);
 
 
            GL.Color3(Color.DodgerBlue);
            GL.TexCoord2(0.0f, 1.0f); GL.Vertex3(-1.0f, -1.0f, 1.0f);
            GL.TexCoord2(1.0f, 1.0f); GL.Vertex3(1.0f, -1.0f, 1.0f);
            GL.TexCoord2(1.0f, 0.0f); GL.Vertex3(1.0f, 1.0f, 1.0f);
            GL.TexCoord2(0.0f, 0.0f); GL.Vertex3(-1.0f, 1.0f, 1.0f);
 
            GL.Color3(Color.Purple);
            GL.TexCoord2(0.0f, 1.0f); GL.Vertex3(-1.0f, 1.0f, -1.0f);
            GL.TexCoord2(1.0f, 1.0f); GL.Vertex3(-1.0f, 1.0f, 1.0f);
            GL.TexCoord2(1.0f, 0.0f); GL.Vertex3(1.0f, 1.0f, 1.0f);
            GL.TexCoord2(0.0f, 0.0f); GL.Vertex3(1.0f, 1.0f, -1.0f);
 
            GL.Color3(Color.ForestGreen);
            GL.TexCoord2(0.0f, 1.0f); GL.Vertex3(1.0f, -1.0f, -1.0f);
            GL.TexCoord2(1.0f, 1.0f); GL.Vertex3(1.0f, 1.0f, -1.0f);
            GL.TexCoord2(1.0f, 0.0f); GL.Vertex3(1.0f, 1.0f, 1.0f);
            GL.TexCoord2(0.0f, 0.0f); GL.Vertex3(1.0f, -1.0f, 1.0f);
 
            GL.End();
 
        }
 
        /// <summary>
        /// The main entry point for the application.
        /// </summary>
 
        [STAThread]
        static void Main()
        {
            // The 'using' idiom guarantees proper resource cleanup.
            // We request 30 UpdateFrame events per second, and unlimited
            // RenderFrame events (as fast as the computer can handle).
            using (Game game = new Game())
            {
                game.Run(30.0);
            }
        }
    }
}

A big thank you to Profet and tksuoran for clearing up errors in this!

Images

Comments

Comment viewing options

Select your preferred way to display the comments and click "Save settings" to activate your changes.
Profet's picture

Nice tutorial about an obscure part of the shader API !

However, it ocured that some things was a bit misleading for me at first, then I would add my contribution :

1) Multitexturing purpose
Multiple TextureUnit is only required when using multiples textures at the same time in a single shader. Use the same TU for all your project textures if you don't use multitexturing.

2) GL.Uniform1 parameter
I found the following usage of GL.Uniform1 very deceptive since the "GenTexture" output id is used instead of the TextureUnit id.
GL.Uniform1(GL.GetUniformLocation(shaderProgramHandle, UniformName), texture);
That seems to be correct (and works) here because the order of texture generation exactly matches the id of used TextureUnit but that's not practical in a dynamic engine where resource generation is unpredictable.

That means that if you are activating TextureUnit.Texture1, you must pass 1 as last parameter to this function.

In a more dynamic way and to avoid passing an additional integer argument to CreateAndBindTexture(), the following code might be used to retrieve the current textureUnit id :

  GL.ActiveTexture(textureUnit);
  GL.GenTextures(1, out texture);
  GL.BindTexture(TextureTarget.Texture2D, texture);
  GL.Uniform1(GL.GetUniformLocation(shaderProgramHandle, UniformName), textureUnit - TextureUnit.Texture0);

3) Dynamic binding
The current example works fine when textures are bound on program's load, but CreateAndBindTexture becomes unpractical when we have to switch textures, as when we render multiple objects using different textures with a single shader.
Then the CreateAndBindTexture method becomes :

  private void CreateTexture(out int texture, Bitmap bitmap)
  {
    //Note really required since we are now binding TextureUnit when drawing objects
    //If left commented out, it will use the current active texture unit, by default TextureUnit.Texture0 if you didn't changed it.
    //GL.ActiveTexture(textureUnit);
 
    // load texture 
    GL.GenTextures(1, out texture);
 
    //Still required else TexImage2D will be applyed on the last bound texture
    GL.BindTexture(TextureTarget.Texture2D, texture);
 
    //Moved to OnRenderFrame()
    //GL.Uniform1(GL.GetUniformLocation(shaderProgramHandle, UniformName), texture);
 
    BitmapData data = bitmap.LockBits(new System.Drawing.Rectangle(0, 0, bitmap.Width, bitmap.Height),
    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);
 
    bitmap.UnlockBits(data);
    GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMinFilter, (int)TextureMinFilter.Linear);
  }

And the drawing part:

  private override void BindTexture(ref int textureId, TextureUnit textureUnit, string UniformName)
  {
    GL.ActiveTexture(textureUnit);
    GL.BindTexture(TextureTarget.Texture2D, textureId);
    GL.Uniform1(GL.GetUniformLocation(this.shaderProgram, UniformName), textureUnit - TextureUnit.Texture0);
  }
 
  protected override void OnRenderFrame(FrameEventArgs e)
  {
    base.OnRenderFrame(e);
    GL.Clear(ClearBufferMask.ColorBufferBit | ClearBufferMask.DepthBufferBit);
    angle += rotation_speed * (float)e.Time;
    float translateby = (float)Math.Sin(DateTime.Now.Second) * 0.05f;
    rotationviewMatrix = Matrix4.CreateTranslation(0f, 0f, translateby);
 
    GL.MatrixMode(MatrixMode.Modelview);
    GL.LoadMatrix(ref modelviewMatrix);
    GL.Rotate(angle, 0.0f, 1.0f, 0.0f);
 
    // [pseudo code]
    //foreach (Cube cube in allCubes)
 
    BindTexture(cube.texture1, TextureUnit.Texture1, "Texture1")
    BindTexture(cube.texture2, TextureUnit.Texture2, "Texture2")
    BindTexture(cube.texture3, TextureUnit.Texture3, "Texture3")
    BindTexture(cube.texture4, TextureUnit.Texture4, "Texture4")
 
    DrawCube(cube);
 
    //endforeach
 
 
    SwapBuffers();
  }

4) TextureUnit ids
The following assumption is not correct outside of this tutorial:
GL.ActiveTexture has to start with TextureUnit.Texture1.
You are free to use any TextureUnit you want, but that was required in this tutorial to match the id generated by the GenTexture function.

5) TextureUnit limitations
Be aware that number of available TextureUnits is hadware dependant, recent have up to 32 units since older might only have 2 of them !

tksuoran's picture

As Profet pointed out, the original post uses GL.Uniform1() incorrectly.

Uniform1() for texture sampler uniforms takes in texture units, not texture objects.
ActiveTexture() selects texture unit, where texture object is then bound.

Also you can use texture0 just fine - you just need to pass in texture unit, not texture object.

tksuoran's picture

Here is some pseudo code:

int textureUnit = 0;
for each texture uniform
{
    GL.Uniform1(slot of uniform, textureIndex);
    GL.ActiveTexture(TextureUnit.Texture0 + textureIndex);
    GL.BindTexture(BindTarget, TextureObject);
    // set texture filter and wrap modes
    ++textureIndex;
}
ZTK's picture

Thank you both very much Profet and tksuoran!

point 1. I don't fully understand. Isn't this a use of multitexturing? After all, each of the four textures is used in the shader, as each texture is tinted differently... Am I missing something?

point 2. Thank you very much! I was confused about this. Your code works great! However, my usage came from the example in the openTK sample brower. Please look at Julia Set Fractal code that comes in the openTK sample browser.. It has the following code

 
            int TextureObject;
 
            GL.ActiveTexture(TextureUnit.Texture0); // select TMU0
            GL.GenTextures(1, out TextureObject);
 
            GL.Uniform1(GL.GetUniformLocation(ProgramObject, "COLORTABLE"), TextureObject);  // <<-- here they are using textureobject from gentextures!!

Am I missing something? It seems in this example they are using the textureObject not the texture unit. Is this a mistake in the example or am I misunderstanding?

point 3. This works perfectly. Thank you so much for the clarificaiton.

point 4. Thank you for helping me understand this. I was very confused, this helps a lot!

point 5. That makes sense.

tksuoran's picture

Consider rendering a car with the following textures:

Texture object 1: Font
Texture object 2: Environment map
Texture object 3: Shadow map
Texture object 4: Skydome
Texture object 5: Car colour texture
Texture object 6: Car normal map

When you render chrome bits of car, you might do the following:
- Texture unit 0 - Texture object 2 environment map
- Texture unit 1 - Texture object 3 shadow map

When you render other parts of the car, you might do the following
- Texture unit 0 - texture object 5 car colour texture
- Texture unit 1 - texture object 6 car normal map
- Texture unit 2 - texture object 3 shadow map

And so on. Accidentally, you can use texture unit X for texture object X - for the first few texture objects. But there is quite limited number of texture units, while you can have hundreds of textures: Add 100 to the texture object names 1..6 above and see what it looks like then. Also, 0 is not a texture object name like other texture object names, so trying to bind texture object name 0 to texture unit 0 is not going to work.

A side note: The above example leaves some room for optimization. You should try to bind same texture object to same unit and not keep switching, so binding shadow map always to texture unit 0 for example may help to get a bit better performance.

ZTK's picture

Thank you tksuroan, I see what you mean.

What I had thought was that textureunits0, 1, 2... etc were in 1-1 correspondence with the actual textures used. So I thought that if you had 20 textures, you would have to use 20 textureunits too.

I guess what I don't understand then is how can I use multiple textures in a single active textureunit, to do what you say. I will do some research into how to do this.

tksuoran's picture

Multitexturing means that you have more than one texture unit, while each texture unit can be using only one texture object.

You can have multiple textures in one texture unit, but not at the same time. So you can have two draw calls with different textures bound to unit 0. But this is not multitexturing.

ZTK's picture

Makes sense. So if I want a shader that is passed 4 different textures (for example to blend them or something) and use them all IN ONE PASS, then I would have to use multitexturing as in the original example, right?

Profet's picture

Yes exactly, multitexturing is the action to blend several textures into a final one, and for that you have to use as many textureunits.
However, as I mentioned in my first post, never use the GenTexture's id as parameter for Uniform1 function, this tutorial is misleading !

To my knowledge, there are few reasons to use multiple TextureUnits :
- multitexturing.
- optimization, if you have a texture that's used very-very-very often, you can reserve a given TU for it to prevent rebinding again and again the same texture (but it might be impractical due to the TU count limitation on some hardware, and the speed gain might also be hardly noticeable).

ZTK's picture

Thank you Profet, I've updated the original post with your corrections to remove the errors you brought up. If I may ask one more question, why is it that you have to do:

GL.Uniform1(GL.GetUniformLocation(shaderProgramHandle, UniformName), textureUnit - TextureUnit.Texture0);

Why can't you do:

GL.Uniform1(GL.GetUniformLocation(shaderProgramHandle, UniformName), textureUnit);

I'm suprised you can't pass a variable of type TextureUnit to the Uniform1 function, and that you have to do a subtraction instead...