You're logged out...?

Writing Custom Materials 2: Adding Textures

A tutorial introducing how to implement custom materials from scratch using textures.



In the previous tutorial, we saw some of the important basics on building materials from scratch by building a flat coloured material. Basic indeed, and not very interesting. To improve on that, we’ll make a small step in the right direction by introducing textures, which in turn exposes a bit more of the material framework’s requirements.


Setting the texture

We can start out exactly the same as last time’s TrivialColorMaterial and TrivialColorPass, naming them TrivialTextureMaterial and TrivialTexturePass, respectively. We’ll highlight the differences necessary to use textures.

Instead of a colour value, we’ll be using a texture as the source for the surface colour. Away3D’s 2D textures are represented by Texture2DBase (and more concretely its subclasses, which can be BitmapData based, ATF-file based or completely custom), so we need a way to assign the texture to the pass. Simply provide a getter and setter:

public function get texture() : Texture2DBase
    return _texture;

public function set texture(value : Texture2DBase) : void
    _texture = value;

No color getter/setter is needed this time, and since we don’t have any more constant data to send to the fragment shader, there’s no need to create a Vector.<Number> for that either.



Providing the AGAL code

As far as the vertex shader is concerned, only one thing changes. Since we’ll need to map a texture on our objects, we need to provide some UV coordinates per vertex and pass them on to the fragment shader where the texture will be sampled.

We’re still using vertex attribute stream 0 for the position, so we’ll use stream 1 for the UV coordinates. We simply pass them on to varying register v0, so the fragment shader can access it, which simply needs to sample the texture we set at texture stream 0.

 * Get the vertex shader code for this shader
override arcane function getVertexCode() : String
    // transform to view space and pass on uv coords to the fragment shader
    return  "m44 op, va0, vc0\n" +
        "mov v0, va1";

 * Get the fragment shader code for this shader
 * @param fragmentAnimatorCode Any additional fragment animation code imposed by the framework, used by some animators. Ignore this for now, since we're not using them.
override arcane function getFragmentCode(fragmentAnimatorCode : String) : String
    // simply set sampled colour from interpolated uv value as output value
    return "tex oc, v0, fs0 <2d, clamp, linear, miplinear>";


Activating the pass

The only render state constant for the entire pass is the texture, which we already decided to place in stream 0. We’ll need to get the actual Stage3D Texture object from the Texture2DBase, which is done as follows:

* Sets the render state which is constant for this pass
* @param stage3DProxy The stage3DProxy used for the current render pass
* @param camera The camera currently used for rendering
override arcane function activate(stage3DProxy : Stage3DProxy, camera : Camera3D) : void
    super.activate(stage3DProxy, camera);
    stage3DProxy._context3D.setProgramConstantsFromVector(Context3DProgramType.FRAGMENT, 0, _fragmentData, 1);



The render method is practically the same as the TrivialColorPass.render method, with one obvious addition: assigning the UV coordinate stream to attribute stream 1 along with the position we already set at stream 0:

renderable.activateUVBuffer(1, stage3DProxy);



Deactivating the pass

While last time we could just skip implementing this step, when using textures this is necessary. Stage3D won’t let us keep textures bound to texture stream slots if they’re not used by the fragment shader. This means that if we’d render an object with our current TrivialTextureMethod followed by a render of an object with TrivialColorMethod, the texture would still be bound in slot 0 resulting in an error. The same is the case for vertex attributes streams, so the UV coordinates assigned to stream 1 need to be unassigned.

 * Clear render state for the next pass.
 * @param stage3DProxy The stage3DProxy used for the current render pass.
override arcane function deactivate(stage3DProxy : Stage3DProxy) : void

    var context : Context3D = stage3DProxy._context3D;

    // clear the texture and stream we set before
    context.setTextureAt(0, null);
    context.setVertexBufferAt(1, null);

“Hold on!”, I hear you say, “We’ve been using vertex stream 0 last time and didn’t need to disable that!” True enough, but since we’re always using the vertex position, we’ll always have something assigned to stream 0 anyway, so disabling it is unnecessary (and it made the previous example a bit simpler).



NOT deactivating the pass

An alternative approach to clear the unused texture and vertex streams is to tell the material framework how many of them are used. In this case, we can simply set the correct values in the constructor:

_numUsedStreams = 2; // vertex position and uv coords
_numUsedTextures = 1; // a single texture

This will allow the material framework to disable streams automatically as needed, and you won’t have to overrride the deactivate method.




The accompanying example code shows how the material can be used, in conjunction with the old TrivialColorMaterial in order to make sure the render state is cleared correctly.


      Listing 1. TrivialTextureMaterial next to TrivialColorMaterial
      Source code.

These first two tutorials already provided most of the info you need to start tinkering around with writing your own custom shaders. However, there’s still some upcoming tutorials to show how to access lights.




Away3D Tutorials Wiki

Member Login