Hi folks,
As I said earlier, I have received some requests on twitter for an article about textures. So here we go.
In this tutorial, I’m going to take the previous article example and modify it so we can see a textured cube instead of a pink one.
By the way, I realized that the previous classes I gave you were incomplete, with some missing imports. Nothing huge actually, just a re-scale function, that is part of our framework here at Marcel. Long story short, sorry about that, I cleaned those one so you may be able to compile right away. And remember, if you have any troubles with any of my examples, just send me a tweet or something.
OK, lets go.
Texture coordinates
In the very firsts round of tutorial, we used to draw triangles with different colors “attached” to each vertices. That was how we could understand that every data that is passed from the vertex shader to the framgment shader is being interpolated.
Using a texture is relatively simple. As every thing we want to use in our shader, we will have to declare it, then upload it to the V-Ram, then allocate it.
The fragment shader, or pixel shader, is runned for every pixels that is being drawn, and it’s purpose is to compute that pixel color. Is previous example, the base color of every pixel was simply stored in a varying register, all we had to do was to, enventually, apply some light on it, then copy the color tu the Output Color register (oc).
Well, the only difference here will be that, for every pixel, the Fragment shader will not receive its color, but will have to sample the texture to get that pixel color.
The question is : how can our shader knows were, on the texture, he is suppose to “look at” to get the pixel color ? Well, this is what texture coordinates does.
Texture coordinate is very simple to understand. We call them U and V, and this is the exact same thing as X and Y in actionscript, except they are not absolute values, but relative values between 0 and 1. This way, you can change your texture size (from a 256*256 to a 512*512 for example) and your texture coordinates will remain the same.

Pretty simple hu ?
So, for a square, you basically have to use the coordinates above. For a triangle, pointing upward, you might have to set your first coordinate to 0.5, 0, so the first point starts at the center of the top edge.
UVs (for now on, I will call texture coordinates UVs), can be really more complex, for instance if you are trying to map a skin and hair texture to a face mesh, but you are not supposed to write them, they will be exported by your 3D software like 3DS Max.
It’s interesting to understand them though, because you can do a lot with UVs. With UVs you can only take a portion of your texture, there is no need to use it all. Ever heard of Sprite sheets ? For those of you that already tried Starling, and that were amazed by the very fast rendering of thousands of animation, this is how an animated “MovieClip” on the GPU is done : upload a single large texture with every frame of your animation, draw it on a square, then set the 4 UV into constants, and change them on every frame ! This way, every frame the GPU will pick a different portion of the image to sample !

An example of sprite sheet. Yep, old game were made that way too !
Texture Mipmapping.
As I am having a hard time trying to explain what is mipmapping, let me quote wikipedia :
In 3D computer graphics texture filtering, mipmaps (also MIP maps) are pre-calculated, optimized collections of images that accompany a main texture, intended to increase rendering speed and reduce aliasing artifacts.
As we saw, for every pixel that is being drawn, the shader will have to sample the texture to get the pixel colors at the corresponding UVs. The first problem is that, with large texture, it will take some time to process. The second, and more important issue is that, at large distance, when your object becomes very small, you might hive issues with texture sampling, resulting in some moire patterns.
The solution is to upload a series of bitmap, each one being half the size of the previous one. The GPU will then automatically choose between two bitmap to sample according to the distance. This is a good optimization technic since each sampling will take less time, but it also takes more V-Ram to store each bitmap. Mipmap used to came with another artifact in old video games where each “layer” of the texture wont blend with each other, as you can see here on the left wall.
The good news is now there is some texture filtering applied so you wont get that ugly rendering. Actually, in the following demo, you will have an option to turn mipmap on and off, and also an option to add a random color to each mipmap level so you can notice the different texture being used.
Let’s start coding
First of all, you can grab the source code. In this archive, you will find the cleaned previous example, updated to use a texture. To upload a texture, you need a BitmapData instance, where the width and height are power of two (256, 512, 1024…). A texture doesn’t need to be square, it can be a rectangle, but remember that your U and V values are always clamped to [0, 1], even on a rectangle shaped texture.
I’m not explaining to you how to get a BitmapData, either load it or embed it as I did.
[Embed(source="../../../../../assets/texture.jpg")]
private var textClass:Class;
private var text:Bitmap = new textClass(); |
The tutorial class takes advantage of my “still-in-progress” Geometry class as the previous tutorial did. A few thing have been improved, you may have a look if you want. Texture declaration, upload and allocation is all done in the __createAndUploadTexture() method.
/**
* Create and upload the texture
*/
private function __createAndUploadTexture():void {
if (texture) {
texture.dispose();
}
texture = context.createTexture(1024, 1024, Context3DTextureFormat.BGRA, false);
// MIPMAP GENERATION
var bmd:BitmapData = text.bitmapData;
var s:int = bmd.width;
var miplevel:int = 0;
while (s > 0) {
texture.uploadFromBitmapData(getResizedBitmapData(bmd, s, s, true, (miplevel != 0 && _mipmapColor.selected) ? Math.random()*0xFFFFFF:0), miplevel);
miplevel++; //miplevel going up by one
s = s * .5; //... and size going down, divided by two.
}
context.setTextureAt(0, texture);
} |
The code is pretty self explanatory. First ask the context to create a texture, it’s the same as asking to create a buffer. The upload part is a bit more complicated though.
Unlike a vertexBuffer, you can upload “several textures” to a single texture, each one corresponding of a smaller texture, for mipmapping purpose. Every time you upload a smaller texture, the “mipmap level” goes up by one, the default being zero.
For instance, if you don’t want to use mipmapping, you can use :
texture.uploadFromBitmapData(bitmapData); |
But if you want to use mipmapping, you will end up using something like :
texture.uploadFromBitmapData(bitmapData512, 0);
texture.uploadFromBitmapData(bitmapData256, 1);
texture.uploadFromBitmapData(bitmapData128, 2);
texture.uploadFromBitmapData(bitmapData64, 3);
// and so on, down to a 1x1 bitmapdata...
texture.uploadFromBitmapData(bitmapData1, 9); |
This is pretty much what my loop does, but instead of creating every texture size by myself, I just use ActionScript to generate them for me.
The rest of the method is allocation, very much like setVertexBufferAt, or setProgram. When you allocate a texture for the program, you will be able to use it in AGAL by calling ”fsx“, so in this case, under fs0. Pretty much the same thing that fragment constants, vertex attributes or whatever…
fs : Fragment Sampler.
AGAL Time
The AGAL code given in the class is almost the same as the previous article AGAL code, so I’m going only to highlights the differences. Well, as always, if you are having troubles with my explanations, feel free to contact me.
Vertex Shader :
code += "mov v0, va1n"; // Interpolate the UVs (va0) into variable register v1
code += "mov v1, va2n"; // Interpolate the normal (va1) into variable register v1 |
In the Vertex Shader, be sure to pass the UVs to the fragment shader. All the rest is the same thing.
Fragment Shader :
In the class, the code changes according to the mipmap combobox, so I will flatten the code here :
"text ft0 v0, fs0 <2d,linear, nomip>n" // NO mipmap |
or
"text ft0 v0, fs0 <2d,linear, miplinear>n" // WITH mipmap |
As you can see, the text opcode takes the following arguments :
text destination, coordinates, texture <options>
Sampler options can be found very easily on google, I will explain them on another article a little bit later.
As you can see, this is really simple. Once sampled, the pixel diffuse color is store on the temporary ft0. Now you can use it as your base color instead of the previous constant that was used (fc4). If you took your last sources instead of those one, make sure you also change this line :
"mul ft2, ft0, ft1 n"+ //multiply fragment color (ft0) by light amount (ft1). |
Or you will still be using the plain color uploaded as a constant (change fc4 by ft0).
Compile and Run, and here you go !
To notice the mipmap effect, zoom out a lot, rotate the cube a little. You can also check “show mipmap colors” so each mipmap levels get a random color.
I hope that will answer questions you had about textures. A little more will come about options, and about cube textures. As always, any feedback is always appreciated, and feel free to contact me if you have troubles !
See you !