Share via


How to: Create Custom Texture Effects

This example builds on the demonstrations How to: Draw Points, Lines, and Other 3D Primitives and How to: Create and Apply Custom Effects to demonstrate how to create an effect to apply a texture to a 3D primitive object.

To compose the texture effect

Compose the effect description using either shader assembly language (ASM) or high-level shader language (HLSL). In this example, the effect file contains a vertex shader to transform the object and a pixel shader to apply the texture to the object.

Note

In this effect file, even though the vertex shader does not manipulate the texture coordinate, it returns the texture coordinate so that it will be available to the pixel shader.

uniform extern float4x4 WorldViewProj : WORLDVIEWPROJECTION;
uniform extern texture UserTexture;

struct VS_OUTPUT
{
    float4 position  : POSITION;
    float4 textureCoordinate : TEXCOORD0;
};

sampler textureSampler = sampler_state
{
    Texture = <UserTexture>;
    mipfilter = LINEAR; 
};
 
VS_OUTPUT Transform(
    float4 Position  : POSITION, 
    float4 TextureCoordinate : TEXCOORD0 )
{
    VS_OUTPUT Out = (VS_OUTPUT)0;

    Out.position = mul(Position, WorldViewProj);
    Out.textureCoordinate = TextureCoordinate;

    return Out;
}

float4 ApplyTexture(VS_OUTPUT vsout) : COLOR
{
    return tex2D(textureSampler, vsout.textureCoordinate).rgba;
}

technique TransformAndTexture
{
    pass P0
    {
        vertexShader = compile vs_2_0 Transform();
        pixelShader  = compile ps_2_0 ApplyTexture();
    }
}

Associate texture coordinates with vertices

  1. Create a vertex declaration for the VertexPositionTexture type.

    cubeVertexDeclaration = new VertexDeclaration(
        graphics.GraphicsDevice, VertexPositionTexture.VertexElements );
    
  2. Initialize the points to be used to draw each side of the cube.

    Vector3 topLeftFront = new Vector3( -1.0f, 1.0f, 1.0f );
    Vector3 bottomLeftFront = new Vector3( -1.0f, -1.0f, 1.0f );
    Vector3 topRightFront = new Vector3( 1.0f, 1.0f, 1.0f );
    Vector3 bottomRightFront = new Vector3( 1.0f, -1.0f, 1.0f );
    Vector3 topLeftBack = new Vector3( -1.0f, 1.0f, -1.0f );
    Vector3 topRightBack = new Vector3( 1.0f, 1.0f, -1.0f );
    Vector3 bottomLeftBack = new Vector3( -1.0f, -1.0f, -1.0f );
    Vector3 bottomRightBack = new Vector3( 1.0f, -1.0f, -1.0f );
    
  3. Initialize the texture coordinates.

    Vector2 textureTopLeft = new Vector2( 0.0f, 0.0f );
    Vector2 textureTopRight = new Vector2( 1.0f, 0.0f );
    Vector2 textureBottomLeft = new Vector2( 0.0f, 1.0f );
    Vector2 textureBottomRight = new Vector2( 1.0f, 1.0f );
    
  4. Declare an array to hold the list of vertices. This array will be used to assign data to the vertex buffer.

    cubeVertices = new VertexPositionTexture[36];
    
  5. Assign the position and texture coordinate data to each element of the vertex array.

    // Vertices for the front of the cube.
    cubeVertices[0] =
        new VertexPositionTexture(
        topLeftFront, textureTopLeft ); // 0
    cubeVertices[1] =
        new VertexPositionTexture(
        bottomLeftFront, textureBottomLeft ); // 1
    cubeVertices[2] =
        new VertexPositionTexture(
        topRightFront, textureTopRight ); // 2
    cubeVertices[3] =
        new VertexPositionTexture(
        bottomRightFront, textureBottomRight ); // 3
    
    // Vertices for the back of the cube.
    cubeVertices[4] =
        new VertexPositionTexture(
        topLeftBack, textureTopRight ); // 4
    cubeVertices[5] =
        new VertexPositionTexture(
        topRightBack, textureTopLeft ); // 5
    cubeVertices[6] =
        new VertexPositionTexture(
        bottomLeftBack, textureBottomRight ); //6
    cubeVertices[7] =
        new VertexPositionTexture(
        bottomRightBack, textureBottomLeft ); // 7
    
    // Vertices for the top of the cube.
    cubeVertices[8] =
        new VertexPositionTexture(
        topLeftFront, textureBottomLeft ); // 8
    cubeVertices[9] =
        new VertexPositionTexture(
        topRightBack, textureTopRight ); // 9
    cubeVertices[10] =
        new VertexPositionTexture(
        topLeftBack, textureTopLeft ); // 10
    cubeVertices[11] =
        new VertexPositionTexture(
        topRightFront, textureBottomRight ); // 11
    
    // Vertices for the bottom of the cube.
    cubeVertices[12] =
        new VertexPositionTexture(
        bottomLeftFront, textureTopLeft ); // 12
    cubeVertices[13] =
        new VertexPositionTexture(
        bottomLeftBack, textureBottomLeft ); // 13
    cubeVertices[14] =
        new VertexPositionTexture(
        bottomRightBack, textureBottomRight ); // 14
    cubeVertices[15] =
        new VertexPositionTexture(
        bottomRightFront, textureTopRight ); // 15
    
    // Vertices for the left side of the cube.
    cubeVertices[16] =
        new VertexPositionTexture(
        topLeftFront, textureTopRight ); // 16
    cubeVertices[17] =
        new VertexPositionTexture(
        bottomLeftFront, textureBottomRight ); // 17
    cubeVertices[18] =
        new VertexPositionTexture(
        topRightFront, textureTopLeft ); // 18
    cubeVertices[19] =
        new VertexPositionTexture(
        bottomRightFront, textureBottomLeft ); // 19
    
  6. Create a vertex buffer to hold the vertex data.

    vertexBuffer = new VertexBuffer( graphics.GraphicsDevice,
        VertexPositionTexture.SizeInBytes * cubeVertices.Length,
        ResourceUsage.None,
        ResourceManagementMode.Automatic
        );
    
  7. Add the data to the vertex buffer.

    vertexBuffer.SetData<VertexPositionTexture>( cubeVertices );
    
  8. Create indices to index into the cubeVertices array.

    cubeIndices = new short[] {  0,  1,  2,  // front face 
                                 1,  3,  2,
                                 4,  5,  6,  // back face
                                 6,  5,  7,
                                 8,  9, 10,  // top face
                                 8, 11,  9,
                                12, 13, 14,  // bottom face
                                12, 14, 15,
                                16, 13, 17,  // left face
                                10, 13, 16,
                                18, 19, 14,  // right face
                                 9, 18, 14 };
    
  9. Create an index buffer to hold the index array data.

    indexBuffer = new IndexBuffer( graphics.GraphicsDevice,
        sizeof( short ) * cubeIndices.Length,
        ResourceUsage.None,
        ResourceManagementMode.Automatic,
        IndexElementSize.SixteenBits
        );
    
  10. Add the data to the index buffer.

    indexBuffer.SetData<short>( cubeIndices );
    

Initialize the texture

  1. Add the texture to the game project. Right-click your game project, click Add, and then click Existing Item. In the Files of type: drop-down list, select All Files. Navigate to your texture file and click Add. In this example, a texture named xna.jpg is added and assigned a corresponding asset name of xna.

    For more information about game asset properties, see Game Asset Properties.

  2. Create a new Texture2D object using the ContentManager.Load<Texture2D> method to load the asset.

Texture2D texture = content.Load<Texture2D>( "xna" );

Set effect parameters

This effect has two parameters, a transformation matrix named WorldViewProj to use in the vertex shader, and the texture named UserTexture to use in the texture sampler to be applied to the pixel shader. Here, these parameters are set to the corresponding objects in the application: the Matrix object named worldViewProjection, and the Texture2D object named texture.

effect.Parameters["WorldViewProj"].SetValue( worldViewProjection );
effect.Parameters["UserTexture"].SetValue( texture );

Choose and apply a texture technique

  1. There is only one technique available in the effect file, so in this example CurrentTechnique is set to TransformAndTexture, which is the name of the technique in the effect file.

    effect.CurrentTechnique = effect.Techniques["TransformAndTexture"];
    
  2. Set the device vertex stream, indices, and vertex declaration to correspond to the vertices for which the texture is to be applied.

    graphics.GraphicsDevice.VertexDeclaration = cubeVertexDeclaration;
    
    graphics.GraphicsDevice.Indices = indexBuffer;
    
    graphics.GraphicsDevice.Vertices[0].SetSource(
        vertexBuffer,
        0,
        VertexPositionTexture.SizeInBytes );
    
  3. Apply the technique by placing the drawing calls between EffectPass.Begin and EffectPass.End inside an Effect.Begin..Effect.End block.

    // This code would go between a device BeginScene-EndScene block.
    effect.Begin();
    foreach (EffectPass pass in effect.CurrentTechnique.Passes)
    {
        pass.Begin();
    
        graphics.GraphicsDevice.DrawIndexedPrimitives( PrimitiveType.TriangleList,
            0,
            0,
            cubeVertices.Length,
            0,
            12
        );
    
        pass.End();
    }
    effect.End();
    

The Complete Example

using System;
using System.Collections.Generic;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Storage;
using Microsoft.Xna.Framework.Content;


public class Game1 : Microsoft.Xna.Framework.Game
{
    Matrix worldViewProjection;
    Effect effect;
    VertexDeclaration cubeVertexDeclaration;
    VertexPositionTexture[] cubeVertices;
    VertexBuffer vertexBuffer;
    IndexBuffer indexBuffer;
    short[] cubeIndices;

    GraphicsDeviceManager graphics;
    ContentManager content;

    public Game1()
    {
        graphics = new GraphicsDeviceManager( this );
        content = new ContentManager( Services );
    }

    protected override void Initialize()
    {
        base.Initialize();
    }

    protected override void LoadGraphicsContent( bool loadAllContent )
    {
        InitializeTransform();

        if (loadAllContent)
        {
            InitializeEffect();
            InitializeCube();
        }
    }

    private void InitializeTransform()
    {
        float tilt = (float)Math.PI / 8.0f;
        // Use the world matrix to tilt the cube along x and y axes.
        Matrix world = Matrix.CreateRotationX( tilt ) *
            Matrix.CreateRotationY( tilt );

        Matrix view = Matrix.CreateLookAt( new Vector3( 0, 0, 5 ), Vector3.Zero,
            Vector3.Up );

        Matrix projection = Matrix.CreatePerspectiveFieldOfView(
            (float)Math.PI / 4.0f,  // 2 PI Radians is 360 degrees, so this is 45 degrees.
            (float)graphics.GraphicsDevice.Viewport.Width /
            (float)graphics.GraphicsDevice.Viewport.Height,
            1.0f, 100.0f );

        worldViewProjection = world * view * projection;

    }

    private void InitializeEffect()
    {
        effect = content.Load<Effect>( "ReallySimpleTexture" );

        Texture2D texture = content.Load<Texture2D>( "xna" );


        effect.Parameters["WorldViewProj"].SetValue( worldViewProjection );
        effect.Parameters["UserTexture"].SetValue( texture );


        effect.CurrentTechnique = effect.Techniques["TransformAndTexture"];


    }

    public VertexPositionTexture[] InitializeCube()
    {
        // Create a vertex declaration for the VertexPositionTexture type.
        cubeVertexDeclaration = new VertexDeclaration(
            graphics.GraphicsDevice, VertexPositionTexture.VertexElements );

        // Initialize the points to be used to draw each side of the cube.
        Vector3 topLeftFront = new Vector3( -1.0f, 1.0f, 1.0f );
        Vector3 bottomLeftFront = new Vector3( -1.0f, -1.0f, 1.0f );
        Vector3 topRightFront = new Vector3( 1.0f, 1.0f, 1.0f );
        Vector3 bottomRightFront = new Vector3( 1.0f, -1.0f, 1.0f );
        Vector3 topLeftBack = new Vector3( -1.0f, 1.0f, -1.0f );
        Vector3 topRightBack = new Vector3( 1.0f, 1.0f, -1.0f );
        Vector3 bottomLeftBack = new Vector3( -1.0f, -1.0f, -1.0f );
        Vector3 bottomRightBack = new Vector3( 1.0f, -1.0f, -1.0f );

        // Initialize the texture coordinates.
        Vector2 textureTopLeft = new Vector2( 0.0f, 0.0f );
        Vector2 textureTopRight = new Vector2( 1.0f, 0.0f );
        Vector2 textureBottomLeft = new Vector2( 0.0f, 1.0f );
        Vector2 textureBottomRight = new Vector2( 1.0f, 1.0f );

        // Create an array to hold the list of vertices.  
        // This array will be used to assign data to the vertex buffer.
        cubeVertices = new VertexPositionTexture[36];

        // Vertices for the front of the cube.
        cubeVertices[0] =
            new VertexPositionTexture(
            topLeftFront, textureTopLeft ); // 0
        cubeVertices[1] =
            new VertexPositionTexture(
            bottomLeftFront, textureBottomLeft ); // 1
        cubeVertices[2] =
            new VertexPositionTexture(
            topRightFront, textureTopRight ); // 2
        cubeVertices[3] =
            new VertexPositionTexture(
            bottomRightFront, textureBottomRight ); // 3

        // Vertices for the back of the cube.
        cubeVertices[4] =
            new VertexPositionTexture(
            topLeftBack, textureTopRight ); // 4
        cubeVertices[5] =
            new VertexPositionTexture(
            topRightBack, textureTopLeft ); // 5
        cubeVertices[6] =
            new VertexPositionTexture(
            bottomLeftBack, textureBottomRight ); //6
        cubeVertices[7] =
            new VertexPositionTexture(
            bottomRightBack, textureBottomLeft ); // 7

        // Vertices for the top of the cube.
        cubeVertices[8] =
            new VertexPositionTexture(
            topLeftFront, textureBottomLeft ); // 8
        cubeVertices[9] =
            new VertexPositionTexture(
            topRightBack, textureTopRight ); // 9
        cubeVertices[10] =
            new VertexPositionTexture(
            topLeftBack, textureTopLeft ); // 10
        cubeVertices[11] =
            new VertexPositionTexture(
            topRightFront, textureBottomRight ); // 11

        // Vertices for the bottom of the cube.
        cubeVertices[12] =
            new VertexPositionTexture(
            bottomLeftFront, textureTopLeft ); // 12
        cubeVertices[13] =
            new VertexPositionTexture(
            bottomLeftBack, textureBottomLeft ); // 13
        cubeVertices[14] =
            new VertexPositionTexture(
            bottomRightBack, textureBottomRight ); // 14
        cubeVertices[15] =
            new VertexPositionTexture(
            bottomRightFront, textureTopRight ); // 15

        // Vertices for the left side of the cube.
        cubeVertices[16] =
            new VertexPositionTexture(
            topLeftFront, textureTopRight ); // 16
        cubeVertices[17] =
            new VertexPositionTexture(
            bottomLeftFront, textureBottomRight ); // 17
        cubeVertices[18] =
            new VertexPositionTexture(
            topRightFront, textureTopLeft ); // 18
        cubeVertices[19] =
            new VertexPositionTexture(
            bottomRightFront, textureBottomLeft ); // 19

        // Create a vertex buffer to hold the vertex data.
        vertexBuffer = new VertexBuffer( graphics.GraphicsDevice,
            VertexPositionTexture.SizeInBytes * cubeVertices.Length,
            ResourceUsage.None,
            ResourceManagementMode.Automatic
            );

        // Add the data to the vertex buffer.
        vertexBuffer.SetData<VertexPositionTexture>( cubeVertices );

        // Create indices to index into the cubeVertices array.
        cubeIndices = new short[] {  0,  1,  2,  // front face 
                                     1,  3,  2,
                                     4,  5,  6,  // back face
                                     6,  5,  7,
                                     8,  9, 10,  // top face
                                     8, 11,  9,
                                    12, 13, 14,  // bottom face
                                    12, 14, 15,
                                    16, 13, 17,  // left face
                                    10, 13, 16,
                                    18, 19, 14,  // right face
                                     9, 18, 14 };

        // Create an index buffer to hold the index data.
        indexBuffer = new IndexBuffer( graphics.GraphicsDevice,
            sizeof( short ) * cubeIndices.Length,
            ResourceUsage.None,
            ResourceManagementMode.Automatic,
            IndexElementSize.SixteenBits
            );

        // Add the data to the index buffer.
        indexBuffer.SetData<short>( cubeIndices );

        return cubeVertices;
    }

    protected override void UnloadGraphicsContent( bool unloadAllContent )
    {
        if (unloadAllContent == true)
        {
            content.Unload();
        }
    }

    protected override void Update( GameTime gameTime )
    {
        // Allows the default game to exit on Xbox 360 and Windows.
        if (GamePad.GetState( PlayerIndex.One ).Buttons.Back == ButtonState.Pressed)
            this.Exit();

        base.Update( gameTime );
    }


    protected override void Draw( GameTime gameTime )
    {
        graphics.GraphicsDevice.Clear( Color.CornflowerBlue );

        graphics.GraphicsDevice.RenderState.CullMode =
            CullMode.CullClockwiseFace;

        graphics.GraphicsDevice.VertexDeclaration = cubeVertexDeclaration;

        graphics.GraphicsDevice.Indices = indexBuffer;

        graphics.GraphicsDevice.Vertices[0].SetSource(
            vertexBuffer,
            0,
            VertexPositionTexture.SizeInBytes );

        // This code would go between a device BeginScene-EndScene block.
        effect.Begin();
        foreach (EffectPass pass in effect.CurrentTechnique.Passes)
        {
            pass.Begin();

            graphics.GraphicsDevice.DrawIndexedPrimitives( PrimitiveType.TriangleList,
                0,
                0,
                cubeVertices.Length,
                0,
                12
            );

            pass.End();
        }
        effect.End();



        base.Draw( gameTime );
    }
}