Layered Textures

In this tutorial topic, we present the use of FbxLayeredTexture to layer four instances of FbxFileTexture. We also expand on the concept of FbxLayerElement to define a mesh's normals and UV coordinates. The code in this topic is written in Python, which applies directly to C++.

Example: Layering Four Textures

Required Resources: from left to right: "one.jpg", "two.jpg", "three.jpg", "four.jpg". Observe that the width and height of these files are multiples of two (128x128), which is recommended for texture dimensions.

Sample Output: This screenshot was taken after running the Python program, and importing the resulting .fbx file in Autodesk MotionBuilder.

Program Summary: The Python program below creates a scene and exports it to a .fbx file. This scene contains a planar mesh textured using an instance of FbxLayeredTexture. This instance of FbxLayeredTexture is composed of four additively blended instances of FbxFileTexture. The FbxLayeredTexture is connected to the diffuse property of an instance of FbxSurfaceLambert. As illustrated by the following diagram, this instance of FbxSurfaceLambert is added to the FbxNode's list of materials.

The planar FbxMesh is set as the FbxNode's attribute. The normal values and UV coordinates for this mesh are set within its 0th FbxLayer using FbxLayerElementNormal and FbxLayerElementUV. The material is bound to the planar polygon when FbxMesh.BeginPolygon() is called with '0' as a parameter. This 0 refers to the (only) material contained in the parent FbxNode's list of materials, i.e. FbxSurfaceLambert mentioned previously.

NOTE:To run this Python program successfully, ensure that the four .jpg images above are in the same folder as this Python program when it is executed.
'''
layeredTextures.py

    > creates a plane in the scene whose four textures are layered 
      one on top of another.

    > requires texture files: one.jpg, two.jpg, three.jpg, four.jpg
'''

import fbx
import FbxCommon

vertices = [fbx.FbxVector4( -5, -5,  0 ), # 0 - vertex index.
            fbx.FbxVector4(  5, -5,  0 ), # 1
            fbx.FbxVector4(  5,  5,  0 ), # 2
            fbx.FbxVector4( -5,  5,  0 )] # 3

normalPosZ = fbx.FbxVector4( 0, 0, 1 ) # positive-Z normal vector. 

# Define the filename and alpha transparency of each texture.
textureFilenames = [ ( 'one.jpg',   0.2 ), 
                     ( 'two.jpg',   0.5 ), 
                     ( 'three.jpg', 0.4 ),
                     ( 'four.jpg',  1.0 ) ]

saveFilename = 'layeredTextures.fbx'

###############################################################
# Helper Function(s).                                         #
###############################################################
def createPlane(pScene):
    ''' Create a planar polygon as a child to the root node. '''
    
    rootNode = pScene.GetRootNode()
    
    #======================================================
    # Node definition and mesh object creation. 
    #======================================================
    # Create the node.
    newNode = fbx.FbxNode.Create( pScene, 'planeNode' )
    rootNode.AddChild( newNode )
    
    # Create the mesh node attribute.
    newMesh = fbx.FbxMesh.Create( pScene, 'planeMesh' )
    newNode.SetNodeAttribute( newMesh )
    
    # Create a Lambertian material and assign it to the mesh's node.
    applyLambertMaterial( pScene, newNode )
    
    # Create and apply the layered texture.
    applyLayeredTexture( pScene, newNode )
    
    # Set the node's shading mode to view textures.
    newNode.SetShadingMode( fbx.FbxNode.eTextureShading )
    
    #======================================================
    # Control points.
    #======================================================
    # Define the vertices of the polygon.
    global vertices
    newMesh.InitControlPoints( 4 )
    newMesh.SetControlPointAt( vertices[0], 0 )
    newMesh.SetControlPointAt( vertices[1], 1 )
    newMesh.SetControlPointAt( vertices[2], 2 )
    newMesh.SetControlPointAt( vertices[3], 3 )
    
    #======================================================
    # Normals.
    #======================================================
    # For normals and UV coordinates, we will be using the
    # Layer indexed at 0. If it doesn't exist, we will create it.
    layer = newMesh.GetLayer( 0 )
    if( not layer ):
        newMesh.CreateLayer()
        layer = newMesh.GetLayer( 0 )
    
    # Create a normal layer element.
    normalLayerElement = fbx.FbxLayerElementNormal.Create( newMesh, 'normals' )
    
    # We want to have one normal for each vertex (or control point),    # so we set the mapping mode to eByControlPoint
    normalLayerElement.SetMappingMode( fbx.FbxLayerElement.eByControlPoint )
    
    # Set the normal values for every control point.
    normalLayerElement.SetReferenceMode( fbx.FbxLayerElement.eDirect )
    
    global normalPosZ # positive-Z normals.
    normalLayerElement.GetDirectArray().Add( normalPosZ )
    normalLayerElement.GetDirectArray().Add( normalPosZ )
    normalLayerElement.GetDirectArray().Add( normalPosZ )
    normalLayerElement.GetDirectArray().Add( normalPosZ )
    
    # Assign the normal layer element to the mesh's layer 0.
    layer.SetNormals( normalLayerElement )
    
    #======================================================
    # Diffuse Channel UV Coordinates.
    #======================================================
    # Given that we are only dealing with one polygon (a square plane),
    # we can simplify the code and map the uv coordinates directly to the
    # four polygon control points.
    uvDiffuseLayerElement = fbx.FbxLayerElementUV.Create( newMesh, 'diffuseUV' )
    uvDiffuseLayerElement.SetMappingMode( fbx.FbxLayerElement.eByPolygonVertex )
    uvDiffuseLayerElement.SetReferenceMode( fbx.FbxLayerElement.eDirect )
    
    # Populate the direct array.
    uv0 = fbx.FbxVector2( 0, 0 )
    uv1 = fbx.FbxVector2( 1, 0 )
    uv2 = fbx.FbxVector2( 1, 1 )
    uv3 = fbx.FbxVector2( 0, 1 )
    uvDiffuseLayerElement.GetDirectArray().Add( uv0 )
    uvDiffuseLayerElement.GetDirectArray().Add( uv1 )
    uvDiffuseLayerElement.GetDirectArray().Add( uv2 )
    uvDiffuseLayerElement.GetDirectArray().Add( uv3 )
    
    # Assign the uv layer element to the mesh's layer 0.
    layer.SetUVs( uvDiffuseLayerElement, fbx.FbxLayerElement.eTextureDiffuse )
    
    #======================================================
    # Polygon Definition.
    #======================================================
    # Create one planar polygon. 
    #    > The 0 in FbxMesh.BeginPolygon() refers to the index of the material to be 
    #      applied to the polygon. Since we only have one material applied to our FbxNode, 
    #      we use index 0.
    newMesh.BeginPolygon( 0 )
    for i in range( 0, 4 ):
        newMesh.AddPolygon( i )
    newMesh.EndPolygon()
    
    return newNode

def applyLambertMaterial( pScene, pNode ):
    ''' Apply a Lambertian material to the node. '''
        
    black = fbx.FbxDouble3( 0, 0, 0 )
    white = fbx.FbxDouble3( 1, 1, 1 )
    
    material = fbx.FbxSurfaceLambert.Create( pScene, 'myMaterial' )
    material.ShadingModel.Set( 'Lambert' ) # could also use 'Phong' if we were using an instance of FbxSurfacePhong
    material.Emissive.Set( black )
    material.Ambient.Set( white )
    material.Diffuse.Set( white )
    material.TransparencyFactor.Set( 0 )
    
    pNode.AddMaterial( material )
    
    
def applyLayeredTexture( pScene, pNode ):
    ''' Apply a layered texture on the node using the given texture filenames. ''' 
    global textureFilenames
    
    textures = []
    
    # Create the individual texture objects to be layered afterwards.
    for i in range( 0, len( textureFilenames ) ):
        
        textureFilename = textureFilenames[i][0]
        textureAlpha = textureFilenames[i][1]
        
        newTexture = fbx.FbxFileTexture.Create( pScene, 'myFileTexture_' +  str( i ) )
        newTexture.SetFileName( textureFilename )
        
        newTexture.SetTextureUse( fbx.FbxTexture.eStandard )
        newTexture.SetMappingType( fbx.FbxTexture.eUV )
        newTexture.SetMaterialUse( fbx.FbxFileTexture.eModelMaterial )
        newTexture.SetSwapUV( False )
        newTexture.SetTranslation( 0.0, 0.0 )
        newTexture.SetScale( 1.0, 1.0 )
        newTexture.SetRotation( 0.0, 0.0 )
        newTexture.Alpha.Set( textureAlpha )
        
        textures.append( newTexture )
        
    # Create the layered texture object, and layer the textures within it.
    layeredTexture = fbx.FbxLayeredTexture.Create( pScene, 'myLayeredTexture' )
     
    for i in range( 0, len( textures ) ):
        texture = textures[ i ]
        layeredTexture.ConnectSrcObject( texture )
        layeredTexture.SetTextureBlendMode( i, fbx.FbxLayeredTexture.eAdditive )
    
    # Connect the layered texture to the node's material indexed at 0.
    # The layered texture is connected on the material's diffuse channel. 
    material = pNode.GetMaterial( 0 )
    material.Diffuse.ConnectSrcObject( layeredTexture )


###############################################################
# Main.                                                       #
###############################################################
def main():
    ''' Main Program. '''
    fbxManager = fbx.FbxManager.Create()
    fbxScene = fbx.FbxScene.Create( fbxManager, '' )
    
    # Create the textured planar mesh.
    newNode = createPlane( fbxScene )

    # Save the file.
    global saveFilename
    FbxCommon.SaveScene( fbxManager, fbxScene, saveFilename, pEmbedMedia=True )
    
    # Clean up.
    fbxManager.Destroy()
    del fbxManager, fbxScene, newNode
    
if __name__ == '__main__':
    main()

See Also