natinusala's picture

Texture Mapping

Hello everyone,

I would like to apply a texture mapping on my cube (to learn OpenGL) in order to have different textures on each face of my cube with only one bitmap file. How can I do that ?

Thanks !


Comments

Comment viewing options

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

You have to define per vertex texture coordinates for your cube and create a texture atlas as bitmap file that contains all desired textures. For a cube, this is quite simple, as you can define the texture coordinates by hand. For more complex objects, you usually use a tool to unwrap your 3D object to a plane. Look up the bold keywords for further explanations. If you want to use a different texture for each face, you will notice that regular per vertex texture coordinates produce false results, as each vertex is shared among 3 faces. Thus, you have to replicate the vertices for each face, i.e. instead of drawing 8 vertices which define a cube, you have to draw 6 * 4 vertices which define the individual faces. Having done that, you can assign texture coordinates.

Have you already managed to map the same texture to each face of the vertex? The texture coordinates for the four vertices of one face would be (0, 0), (1, 0), (1, 1), (0, 1) - the float values between 0 and 1 represent the position inside a texture, i.e. (0, 0) is the lower left corner, (1, 1) is the upper right corner of the texture, (0.5, 0.5) is the center.

To map different textures, you divide your texture atlas into different sectors. For the 6 faces of your cube you could use a texture with a width divisible by 6, e.g. 6 * 256 = 1536 x 256 pixels. Now you can assign the texture coordinates to your face vertices as follows:

First face:
Lower left vertex: (0, 0)
Lower right vertex: (1/6, 0)
Upper right vertex: (1/6, 1)
Upper left vertex: (0, 1)

Second face:
Lower left vertex: (1/6, 0)
Lower right vertex: (2/6, 0)
Upper right vertex: (2/6, 1)
Upper left vertex: (1/6, 1)

...

Sixth face:
Lower left vertex: (5/6, 0)
Lower right vertex: (1, 0)
Upper right vertex: (1, 1)
Upper left vertex: (5/6, 1)

Is this clear?

natinusala's picture

Thanks ! So I have to define a Texture Matrix and what's next ? Could you give me a little example of the texturing part of a cube ?

mOfl's picture

No, the texture matrix is not what you want. You use a texture matrix if you want to transform a texture on the object, not for regular texture mapping. Instead, you have to use UV mapping (that's the thingy with the texture coordinates). You pass the per-vertex texture coordinates to the video card as attributes to your vertices. Doing this in a forward compatible way:

uint[] indices = new uint[] {
    // front face
    0, 1, 2, 2, 3, 0,
 
    // back face
    4, 5, 6, 6, 7, 4,
 
    // left face
    8, 9, 10, 10, 11, 8,
 
    // right face
    12, 13, 14, 14, 15, 12,
 
    // top face
    16, 17, 18, 18, 19, 16,
 
    // bottom face
    20, 21, 22, 22, 23, 20
};
 
Vector3[] normals = new Vector3[] {
    // front face
    new Vector3(0.0f, 0.0f, 1.0f),
    new Vector3(0.0f, 0.0f, 1.0f),
    new Vector3(0.0f, 0.0f, 1.0f),
    new Vector3(0.0f, 0.0f, 1.0f),
 
    // back face
    new Vector3(0.0f, 0.0f, -1.0f),
    new Vector3(0.0f, 0.0f, -1.0f),
    new Vector3(0.0f, 0.0f, -1.0f),
    new Vector3(0.0f, 0.0f, -1.0f),
 
    // left face
    new Vector3(1.0f, 0.0f, 0.0f),
    new Vector3(1.0f, 0.0f, 0.0f),
    new Vector3(1.0f, 0.0f, 0.0f),
    new Vector3(1.0f, 0.0f, 0.0f),
 
    // right face
    new Vector3(-1.0f, 0.0f, 0.0f),
    new Vector3(-1.0f, 0.0f, 0.0f),
    new Vector3(-1.0f, 0.0f, 0.0f),
    new Vector3(-1.0f, 0.0f, 0.0f),
 
    // top face
    new Vector3(0.0f, 1.0f, 0.0f),
    new Vector3(0.0f, 1.0f, 0.0f),
    new Vector3(0.0f, 1.0f, 0.0f),
    new Vector3(0.0f, 1.0f, 0.0f),
 
    // bottom face
    new Vector3(0.0f, -1.0f, 0.0f),
    new Vector3(0.0f, -1.0f, 0.0f),
    new Vector3(0.0f, -1.0f, 0.0f),
    new Vector3(0.0f, -1.0f, 0.0f)
};
 
Vector4[] vertices = new Vector4[] {
    // front face
    new Vector4(-1.0f, 1.0f, 1.0f, 1.0f),
    new Vector4(-1.0f, -1.0f, 1.0f, 1.0f),
    new Vector4(1.0f, -1.0f, 1.0f, 1.0f),
    new Vector4(1.0f, 1.0f, 1.0f, 1.0f),
 
    // back face
    new Vector4(1.0f, 1.0f, -1.0f, 1.0f),
    new Vector4(1.0f, -1.0f, -1.0f, 1.0f),
    new Vector4(-1.0f, -1.0f, -1.0f, 1.0f),
    new Vector4(-1.0f, 1.0f, -1.0f, 1.0f),
 
    // left face
    new Vector4(1.0f, 1.0f, 1.0f, 1.0f),
    new Vector4(1.0f, -1.0f, 1.0f, 1.0f),
    new Vector4(1.0f, -1.0f, -1.0f, 1.0f),
    new Vector4(1.0f, 1.0f, -1.0f, 1.0f),
 
    // right face
    new Vector4(-1.0f, 1.0f, -1.0f, 1.0f),
    new Vector4(-1.0f, -1.0f, -1.0f, 1.0f),
    new Vector4(-1.0f, -1.0f, 1.0f, 1.0f),
    new Vector4(-1.0f, 1.0f, 1.0f, 1.0f),
 
    // top face
    new Vector4(-1.0f, 1.0f, -1.0f, 1.0f),
    new Vector4(-1.0f, 1.0f, 1.0f, 1.0f),
    new Vector4(1.0f, 1.0f, 1.0f, 1.0f),
    new Vector4(1.0f, 1.0f, -1.0f, 1.0f),
 
    // bottom face
    new Vector4(-1.0f, -1.0f, 1.0f, 1.0f),
    new Vector4(-1.0f, -1.0f, -1.0f, 1.0f),
    new Vector4(1.0f, -1.0f, -1.0f, 1.0f),
    new Vector4(1.0f, -1.0f, 1.0f, 1.0f)
};
 
Vector2[] texCoords = new Vector2[] {
    // front face
    new Vector2(0.0f, 0.0f),
    new Vector2(0.0f, 1.0f),
    new Vector2(1.0f, 1.0f),
    new Vector2(1.0f, 0.0f),
 
    // back face
    new Vector2(0.0f, 0.0f),
    new Vector2(0.0f, 1.0f),
    new Vector2(1.0f, 1.0f),
    new Vector2(1.0f, 0.0f),
 
    // left face
    new Vector2(0.0f, 0.0f),
    new Vector2(0.0f, 1.0f),
    new Vector2(1.0f, 1.0f),
    new Vector2(1.0f, 0.0f),
 
    // right face
    new Vector2(0.0f, 0.0f),
    new Vector2(0.0f, 1.0f),
    new Vector2(1.0f, 1.0f),
    new Vector2(1.0f, 0.0f),
 
    // top face
    new Vector2(0.0f, 0.0f),
    new Vector2(0.0f, 1.0f),
    new Vector2(1.0f, 1.0f),
    new Vector2(1.0f, 0.0f),
 
    // bottom face
    new Vector2(0.0f, 0.0f),
    new Vector2(0.0f, 1.0f),
    new Vector2(1.0f, 1.0f),
    new Vector2(1.0f, 0.0f)
};
 
// vertex array object
GL.GenVertexArrays(1, out this.vertexArray);
GL.BindVertexArray(this.vertexArray);
 
// get the locations of the shader fields which you want to bind your buffers to
// (this assumes that you have the in fields vec4 inPosition, vec3 inNormal and vec2 inTexCoord in your shader!)
int positionLoc = GL.GetAttribLocation(this.shader, "inPosition");
int normalLoc = GL.GetAttribLocation(this.shader, "inNormal");
int texCoordLoc = GL.GetAttribLocation(this.shader, "inTexCoord");
 
// vertex buffer
GL.GenBuffers(1, out this.vertexBuffer);
GL.BindBuffer(BufferTarget.ArrayBuffer, this.vertexBuffer);
GL.BufferData<Vector4>(
    BufferTarget.ArrayBuffer,
    new IntPtr(this.vertices.Length * Vector4.SizeInBytes),
    this.vertices,
    BufferUsageHint.StaticDraw);
GL.EnableVertexAttribArray(positionLoc);
GL.VertexAttribPointer(positionLoc, 4, VertexAttribPointerType.Float, false, Vector4.SizeInBytes, 0);
 
// normal buffer
GL.GenBuffers(1, out this.normalBuffer);
GL.BindBuffer(BufferTarget.ArrayBuffer, this.normalBuffer);
GL.BufferData<Vector3>(
    BufferTarget.ArrayBuffer,
    new IntPtr(this.vertices.Length * Vector3.SizeInBytes),
    this.normals,
    BufferUsageHint.StaticDraw);
GL.EnableVertexAttribArray(normalLoc);
GL.VertexAttribPointer(normalLoc, 3, VertexAttribPointerType.Float, false, Vector3.SizeInBytes, 0);
 
// texcoord buffer
GL.GenBuffers(1, out this.texCoordBuffer);
GL.BindBuffer(BufferTarget.ArrayBuffer, this.texCoordBuffer);
GL.BufferData<Vector2>(
    BufferTarget.ArrayBuffer,
    new IntPtr(this.vertices.Length * Vector2.SizeInBytes),
    this.texCoords,
    BufferUsageHint.StaticDraw);
GL.EnableVertexAttribArray(texCoordLoc);
GL.VertexAttribPointer(texCoordLoc, 2, VertexAttribPointerType.Float, false, Vector2.SizeInBytes, 0);
 
// index buffer
GL.GenBuffers(1, out this.elementBuffer);
GL.BindBuffer(BufferTarget.ElementArrayBuffer, this.elementBuffer);
GL.BufferData(BufferTarget.ElementArrayBuffer, new IntPtr(sizeof(uint) * this.indices.Length), this.indices, BufferUsageHint.StaticDraw);
 
GL.BindBuffer(BufferTarget.ElementArrayBuffer, 0);
GL.BindBuffer(BufferTarget.ArrayBuffer, 0);
GL.BindVertexArray(0);

The last part is the initialization of a Vertex Array Object and Vertex Buffer Objects bound to it. You will find a lot of OpenGL sample code using GL.Begin() ... GL.End() calls, but you should not do this anymore as it is deprecated. Therefore, the forward compatible and much more faster method of rendering is using those Vertex Array and Vertex Buffer Objects. If you have trouble drawing from the buffers, have a look at some sample code posted here in the forum, there are little tutorial pages that cover the usage of VAOs and VBOs. The procedure is something like

// bind your Vertex Array Object                
GL.BindVertexArray(this.vertexArray);
 
// bind your index buffer
GL.BindBuffer(BufferTarget.ElementArrayBuffer, this.elementBuffer);
 
// bind your texture atlas to bind index 0
GL.Enable(EnableCap.Texture2D);
GL.ActiveTexture(0);
GL.BindTexture(TextureTarget.Texture2D, this.textureIndex);
 
// bind your shader
GL.UseProgram(this.shader);
 
// draw from the buffers bound to the Vertex Array Object
GL.DrawElements(BeginMode.Triangles, this.indices.Length, DrawElementsType.UnsignedInt, IntPtr.Zero);
 
// unbind your Vertex Array Object
GL.BindVertexArray(0);
 
// unbind your index buffer
GL.BindBuffer(BufferTarget.ElementArrayBuffer, 0);
 
// unbind the index 0 texture
GL.ActiveTexture(0);
GL.BindTexture(TextureTarget.Texture2D, 0);
GL.Disable(EnableCap.Texture2D);
 
// unbind your shader
GL.UseProgram(0);

I hope this all works, I copy&pasted a bit together from my rendering framework which is much more complicated and also based on CG as shader language (and I assume you want to use GLSL), so I don't know for sure if I got the shader binding calls right. As I said, there is a lot of sample code on the forum and, of course, all over the internet. This is not an OpenTK matter, but standard OpenGL procedure, so you will find tons of useful information.

Oh, and one more thing: You might notice that the code is quite sophisticated and there are a lot of new GL calls if you are a beginner. Let me tell you that texturing a cube in OpenGL is not a trivial task or a Hello World example which you can do without knowledge. If you don't get it running or don't understand the code I wrote, I recommend you to start somewhere lower: First draw a simple 2D triangle according to some online tutorial with vertex and fragment shader to the screen. Nothing fancy, but you get a feeling for the shader stuff and know that this is working. Then, try to draw this triangle with Vertex Buffer Objects and a Vertex Array Object. Then, extend this to the third dimension, which is already challenging as you have to use matrix multiplications. Note that you should do this in the vertex shader as well and not with the deprecated OpenGL matrix stack. Once you have managed to work with a model view projection matrix in the shader, you can try texturing the triangle. You will find out what states you need to enable and how to bind a texture (correctly) to the shader and how to do texture lookups in your shader. Having accomplished that, you can try to draw a simple cube, still using shaders and VAO/VBOs. The next step would be to texture all faces of the cube with the same texture. This is what the cube code I posted above does. And once you have accomplished this, it would be time to extend this to the possibility of mapping different textures to the faces with changing the texture coordinates according to my first post. If you are a beginner, this will be quite a way to go, but there are some very good tutorials out there that guide you. You can do yourself a favor by never getting used to the deprecated immediate mode. The first thing you should regard for this is not to follow tutorials which are several years old. Search for "OpenGL 3" tutorials, if possible. When going through foreign code, always have a look at http://www.khronos.org/files/opengl-quick-reference-card.pdf - each call which is marked blue in this file is deprecated and should not be used. I'm afraid this will eliminate the majority of all OpenGL tutorials, but believe me, if you have the chance to avoid getting used to the deprecated, inefficient (although much more easy to use) stuff, you should seize it.

Hope that helps. Don't hesitate to ask in the forum if you need help with a specific topic, but keep in mind that you are not limited to the OpenTK forum when searching, as these are native OpenGL topics which a lot of people have written stuff and articles about.