Tutorials
Articles
OpenGL Demos
Games
OpenGL Misc
MSG Board
About
Donate
Links
Home
Megabyte Softworks
C++, OpenGL, Algorithms




Current series: OpenGL 3.3
(Return to list of OpenGL 3.3 tutorials)

Download (2.16 MB)
4632 downloads. 2 comments
11.) Multitexturing

Welcome to the eleventh from OpenGL 3.3 series tutorial. This one took me longer to write (almost 4 weeks!!!), not because its somehow extremely complex or anything, its just because more than 2 weeks I've been almost constantly in my work, because of unstoppable deadline LULZ, and then I needed to take some time to recover from all this programming overload. I should be back to my normal writing tempo, starting from this tutorial, which means cca 1 tutorial in 8-10 days. I'm also currently working on my Bachelor's Thesis, so I need to dedicate some of my free time there as well. Hope this infinite waiting didn't discourage you.

This tutorial was originally intended to be fog tutorial, but because shader fog isn't that as easy as multitexturing, and I suppose writing the article would take me longer, and I was already behind my personal tutorial writing schedule, I decided to go with something easier this time. And the topic for today is multitexturing.

New file structure of tutorials

Starting from this tutorial, I decided to change file structure of this tutorial. Now executable files are in bin directory, and also Debug mode working directory is bin. It means now, that after you open solution, your newly created debug exe will be created in bin directory, its name will be ProjectName_d.exe, and it will start from bin directory. Advantage of this is, that there is no need to copy DLL and data files anywhere. I'll show you how its done in Visual Studio 2008, it's only Visual Studio related stuff, but it's really useful for you to know this. So first step is to click on Project Properties and then setting up output directory - directory where exe is generated - we'll set it to directory, where solution file is located, and its bin subdirectory:

Second step is to go to Debugging Tab in Configuration Properties and setting proper working directory of debug version, so that when you press F5 to start debugging your code, it will start in this directory, where all application data and DLLs are located:

Third and the last step for this is to go to Linker Tab / General in Configuration Properties and setting output file name to project name, plus '_d' appendix, meaning that its debug version of application.

You must do analogous thing in Release configuration of your solution. Only thing that changes is Linker output file name. In Release, it will be without '_d' appendix. And that's all. Now we can proceed with actual multitexturing tutorial.

Multitexturing

Multitexturing is process of aplying multiple textures on polygon. This means, that we'll somehow mix colors from all used textures before applying final color to polygon. And we will specify how in fragment shader. In this tutorial, we'll have a ground texture, that will have both grass texture and dry, cracked ground texture applied to it. You are able to control amount of both textures by pressing keys 'G' (more grass) or 'H' (less grass, more draugHt LULZ). The vertex shader remains unchanged now, let's have a look at fragment shader code now:


#version 330

in vec2 texCoord;
smooth in vec3 vNormal;
out vec4 outputColor;

uniform sampler2D gSamplers[2];
uniform float fTextureContributions[2];
uniform vec4 vColor;
uniform int numTextures;

struct SimpleDirectionalLight
{
   vec3 vColor;
   vec3 vDirection;
   float fAmbientIntensity;
};

uniform SimpleDirectionalLight sunLight;

void main()
{
   outputColor = vec4(0.0, 0.0, 0.0, 0.0);
   for(int i = 0; i < numTextures; i++)
   {
      if(i == 0)
      {
         vec4 vTexColor = texture2D(gSamplers[0], texCoord);
         outputColor += vTexColor*fTextureContributions[0];
      }
      else
      {
            vec4 vTexColor = texture2D(gSamplers[1], texCoord);
            outputColor += vTexColor*fTextureContributions[1];
      }
   }
   float fDiffuseIntensity = max(0.0, dot(normalize(vNormal), -sunLight.vDirection));
   outputColor *= vColor*vec4(sunLight.vColor*(sunLight.fAmbientIntensity+fDiffuseIntensity), 1.0);
}

Now we have an uniform array of samplers, with which we will access the texture data. Important addition is also numTextures uniform, that represents how many textures we are using to produce final image. Last important change is fTextureContributions uniform array. It's an array of floats, ranging from 0.0 to 1.0, that says, how much of texture color to add to final image, where 0.0 is no texture contribution in final image and 1.0 as full contribution in final image (whole texel will be added).

Let's have a look at shader body. We can see, that we loop through number of textures used, adding as much as is needed to outputColor. Now the main question arise: why I didn't access array of samplers and texture contributions normally with i, but rather handle multiple cases of i and then giving constants to array indices? Well, the answer is, because you cannot access uniform arrays dynamically. I don't know why it's like that, but I've been googling a while and this seems to be the workaround. I have tried this tutorial on my notebook with GeForce 9300M, and on AMD Radeon HD4850, and the result was same. It's quite irritating I think, this way you limit yourself with maximal number of textures statically, not dynamically. I didn't bother to try it on other nVidia and AMD cards, because it seems it's in specification of GLSL, that one does not simply access uniform arrays with non-constant expressions:

I hope it made you laugh LULZ ! After reconciling with this fact, let's have a look at multi-texture rendering. Here is a ground rendering part of renderScene function:


void renderScene(LPVOID lpParam)
{
   //...
   
   // Render ground

   spDirectionalLight.setUniform("numTextures", 2);
   // Set sampler 0 texture unit 0
   spDirectionalLight.setUniform("gSamplers[0]", 0);
   // Set sampler 1 texture unit 1
   spDirectionalLight.setUniform("gSamplers[1]", 1);
   // Set contribution according to desertification factor
   spDirectionalLight.setUniform("fTextureContributions[0]", 1.0f-fDryAmount);
   spDirectionalLight.setUniform("fTextureContributions[1]", fDryAmount);
   // Bind texture 0 to texture unit 0
   tTextures[0].bindTexture(0);
   // Bind texture 1 to texture unit 1
   tTextures[1].bindTexture(1);

   glDrawArrays(GL_TRIANGLES, 36, 6);

   //...
}

As you see, we're binding grass texture to texture unit 0, then binding dry ground texture to texture unit 1. We also set their contributions depending on fDryAmount - dry ground has fDryAmount, and grass has the rest (1.0f-fDryAmount). Hope this makes sense. But so far, no new OpenGL function was introduced. Actually, it's hidden inside bindTexture function. So let's have a look at it:


void CTexture::bindTexture(int iTextureUnit)
{
   glActiveTexture(GL_TEXTURE0+iTextureUnit);
   glBindTexture(GL_TEXTURE_2D, uiTexture);
   glBindSampler(iTextureUnit, uiSampler);
}

With first function glActiveTexture, we tell OpenGL, which texture unit we're going to change or bind textures to. Parameter is OpenGL constant GL_TEXTUREn, with n going from 0 to max texture units supported by graphics hadware minus 1. You can query for that number using glGetInteger with parameter GL_MAX_TEXTURE_IMAGE_UNITS, actually, it's done in renderScene function when printing fonts, so you can have a look at it. There are also other constants, like GL_MAX_TEXTURE_UNITS (maximal number of texture units, when using fixed functionality, minimum is 2, when using OpenGL 3.3 context, this returns invalid values on ATI/AMD cards), GL_MAX_VERTEX_TEXTURE_IMAGE_UNITS (maximal number of texture units accessible by vertex shader, minimum is 0), and GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS (total number of texture units accessible by OpenGL, minimum is 2). After calling glActiveTexture, we proceed by binding texture itself. And the last thing is binding texture sampler to specific texture unit. It's done by glBindSampler, first parameter is texture unit index, second is sampler. Samplers have been explained in detail in Tutorial 6 - Textures, so if you're not feeling "samply" (know what samplers are LULZ), have a look there.

That should do it. That's all for multitexturing. As you can see, it's nothing extremely difficult, just know which functions are used for multitexturing, and that's probably all. But now, we are going to discuss very annoying thing, that occured in my previous tutorials.

Depth buffer flickering

I'm not sure if after reading caption you know what I'm thinking of. When moving far away from scene, like in Skybox tutorial, the further from scene the camera was, the more have objects started to flicker and their parts randomly dissapearing. Well, the solution to this problem is luckily nothing difficult, problem with that was that I set near clipping plane too close (it was 0.001f), and the closer to zero that number, the less precise our depth buffer is (on OpenGL.org I read, that the precision decreases dramatically as this number approaches zero). So to get rid of it, we just set near clipping plane to somewhat bigger number, but not too much. I decided to go for 0.5f (this function is called when receiving WM_SIZE message in win_OpenGLApp.cpp). Problem is gone now. You can see the difference in this picture:

If you were to create a real game, you would never move so close to wall, that it would dissapear, because you probably won't be able to pass the walls. So having near clipping plane a little further than 0.001f is definitely something that will only help the scene rendering.

Conclusion

This is the result of today's efforts (Don't forget to play around with 'G' and 'H' keys to alter desertification):

Eleventh tutorial is over. By learning multitexturing, you received 1000 EXP and you just leveled up in your OpenGL skills LULZ! Next tutorial is going to be about fog, and later we will expand multitexturing concept to texture splatting to create seamless transitions between two different texture on the surface (like when rendering a dirt path in a grass polygon). Thank you for attention. Comment if you've got any questions.



Download (2.16 MB)
4632 downloads. 2 comments
 
Name:

E-mail:
(Optional)
Entry:

Enter the text from image:



Smileys




Batch2 on 29.04.2014 16:19:07
I think this is related to what Draco already said. You forget to divide the outputcolour after your additions by numTextures.
Draco Nared (draconared@gmail.com) on 13.05.2013 20:50:31
Hey
I think you have a little bug in your fragment shader code. You're adding the color to the outputColor two times. This way you get solid black surface (when it should be dry ground texture) and veeeery bright grass texture (when it should be normal grass texture).
However, I think I managed to fix it. My solution is (instead of loop):
if (numTextures > 1){
vec4 vTexColor = texture2D(gSamplers[0], texCoord);
outputColor += vTexColor*fTextureContributions[0];
vTexColor = texture2D(gSamplers[1], texCoord);
outputColor += vTexColor*fTextureContributions[1];
outputColor /= 2;
}
else {
vec4 vTexColor = texture2D(gSamplers[0], texCoord);
outputColor += vTexColor*fTextureContributions[0];
}

Best regards,
Draco
Jump to page:
1