Tutorial #4 - Texture Mapping

19. April 2010 22:29 by Jeromy Walsh in Tutorials  //  Tags: ,   //   Comments (15)

In this tutorial, we go from using user-provided arrays with simple, colored primitives, to using buffered primitives with full texture mapping and filtering. We will also look at some of the changes in the new GraphicsDevice class.

In this series of tutorials, I have so far been following the path of the NeHe OpenGL tutorials. The idea was to demonstrate how easy it is to complete those same tutorials using XNA. However, starting with this fourth tutorial I am diverging from the NeHe tutorials because they, at times, develop a little slower than many would like, and also don’t go into enough depth. I will on occasion go back and use the NeHe tutorials for inspiration, but in general, this tutorial series will now follow its own path.

For those who are familiar with the NeHe series of tutorials, this tutorial will cover roughly the same material as covered in the NeHe tutorials 4-6 (and some of 7). In specific, we are going to be drawing full 3D objects now instead of just single primitives, we are going to be doing a bit more rotation and manipulation of the world transforms of our objects, and we are going to be performing both texture sampling and filtering as part of our coverage of texture mapping.

As usual, all rendering will be done with HLSL and our own custom effect classes, rather than using the pre-built *Effect classes which ship with XNA 4.0. Note, however, that everything we are going to do in this tutorial is possible with the BasicEffect class. With no further ado, let's get started.

The Sample Framework

The first thing you will need to do is grab hold of the sample framework. If you have been following along with the previous tutorials, you have seen the first version of the sample framework, which was provided as part of Tutorial #1. However, because we will usually call the ResetProjection method from within our OnResize handler, I have gone ahead, added that to the base code, and provided a second download. You will need to get the updated base code here.

The Game Class

With the new base code in hand, it is time to get started making changes to Game1. The first thing we are going to do is add some new fields. We need an instance of the DefaultEffect class, which we will use to perform our rendering, we will need some matrices to perform some transformations, and we will need a pair of buffers for each of our 3D objects.

public class Game1 : Microsoft.Xna.Framework.Game
{
	GraphicsDeviceManager graphics;
	KeyboardState prevKeyboardState = Keyboard.GetState();

	DefaultEffect effect;
	Matrix triangleTransform;
	Matrix rectangleTransform;

	VertexBuffer pyramidVB;
	IndexBuffer pyramidIB;
	VertexBuffer boxVB;
	IndexBuffer boxIB;
	TextureFilter textureFilter = TextureFilter.Linear;

	private float triangleAngle;
	private float rectangleAngle;
	bool isAnimating = true;
	float depth = -6.0f;

Part of the purpose of this tutorial is for you to see how a filtering mode changes the appearance of textures on your primitives. In order to accomplish this, you need to be able to switch between different filtering methods at run-time. The textureFilter field will identify what the current filtering method is. Another objective of this tutorial is to have a box and pyramid rotating around in circles. We will use the two angle fields to identify the current rotation of the objects. The associated isAnimating field will determine whether the game "is animating".

The first method we are going to look at is the ResetTranslation method. In previous projects, the transformation matrices were built once in the Initialize method. In this project, we are going to allow the user to move the object closer or further away in order to get a better look at the texture mapping. To make this easier, I have provided a ResetTranslation method which will be called both from within Initialize, as well as from within Update.

protected void ResetTranslation()
{
	triangleTransform  = Matrix.CreateTranslation(new Vector3(-1.5f, 0.0f, depth));
	rectangleTransform = Matrix.CreateTranslation(new Vector3(1.5f, 0.0f, depth));
}

Now let's take a look at the Initialize method. The first thing we do is call ResetTranslation. This creates the world transformations for both the pyramid and the box, based on the starting depth of -6.0f. The next part of Initialize is the creation of two vertex arrays and index lists - one set for the pyramid, and one set for the box. In the previous tutorial, we used the built-in VertexPositionColor structure to represent our 3D vertices. However, because we are planning to use texture mapping in this project, we will need a different vertex structure. Specifically, one that has a position for each vertex and a set of texture coordinates (sometimes called UV coordinates), which identify which texel within a texture we'd like to map to the current vertex. Fortunately, XNA once again comes through with a pre-built structure called VertexPositionTexture.

protected override void Initialize()
{		
	ResetTranslation();
	
	Vector2 topLeft = new Vector2(0.0f, 0.0f);		
	Vector2 topRight = new Vector2(1.0f, 0.0f);
	Vector2 bottomLeft = new Vector2(0.0f, 1.0f);
	Vector2 bottomRight = new Vector2(1.0f, 1.0f);

	VertexPositionTexture[] triangleData = new VertexPositionTexture[]
	{
		new VertexPositionTexture(new Vector3(1.0f, -1.0f, 1.0f), bottomRight),
		new VertexPositionTexture(new Vector3(-1.0f, -1.0f, 1.0f), bottomLeft),
		new VertexPositionTexture(new Vector3(0.0f, 1.0f, 0.0f), topRight),

		new VertexPositionTexture(new Vector3(1.0f, -1.0f, -1.0f), bottomRight),
		new VertexPositionTexture(new Vector3(1.0f, -1.0f, 1.0f), bottomLeft),
		new VertexPositionTexture(new Vector3(0.0f, 1.0f, 0.0f), topRight),

		new VertexPositionTexture(new Vector3(-1.0f, -1.0f, -1.0f), bottomRight),
		new VertexPositionTexture(new Vector3(1.0f, -1.0f, -1.0f), bottomLeft),
		new VertexPositionTexture(new Vector3(0.0f, 1.0f, 0.0f), topRight),

		new VertexPositionTexture(new Vector3(-1.0f, -1.0f, 1.0f), bottomRight),
		new VertexPositionTexture(new Vector3(-1.0f, -1.0f, -1.0f), bottomLeft),
		new VertexPositionTexture(new Vector3(0.0f, 1.0f, 0.0f), topRight),
	};

	VertexPositionTexture[] boxData = new VertexPositionTexture[]
	{
		// Front Surface
		new VertexPositionTexture(new Vector3(-1.0f, -1.0f, 1.0f),bottomLeft),
		new VertexPositionTexture(new Vector3(-1.0f, 1.0f, 1.0f),topLeft), 
		new VertexPositionTexture(new Vector3(1.0f, -1.0f, 1.0f),bottomRight),
		new VertexPositionTexture(new Vector3(1.0f, 1.0f, 1.0f),topRight),  

		// Front Surface
		new VertexPositionTexture(new Vector3(1.0f, -1.0f, -1.0f),bottomLeft),
		new VertexPositionTexture(new Vector3(1.0f, 1.0f, -1.0f),topLeft), 
		new VertexPositionTexture(new Vector3(-1.0f, -1.0f, -1.0f),bottomRight),
		new VertexPositionTexture(new Vector3(-1.0f, 1.0f, -1.0f),topRight), 

		// Left Surface
		new VertexPositionTexture(new Vector3(-1.0f, -1.0f, -1.0f),bottomLeft),
		new VertexPositionTexture(new Vector3(-1.0f, 1.0f, -1.0f),topLeft),
		new VertexPositionTexture(new Vector3(-1.0f, -1.0f, 1.0f),bottomRight),
		new VertexPositionTexture(new Vector3(-1.0f, 1.0f, 1.0f),topRight),

		// Right Surface
		new VertexPositionTexture(new Vector3(1.0f, -1.0f, 1.0f),bottomLeft),
		new VertexPositionTexture(new Vector3(1.0f, 1.0f, 1.0f),topLeft),
		new VertexPositionTexture(new Vector3(1.0f, -1.0f, -1.0f),bottomRight),
		new VertexPositionTexture(new Vector3(1.0f, 1.0f, -1.0f),topRight),

		// Top Surface
		new VertexPositionTexture(new Vector3(-1.0f, 1.0f, 1.0f),bottomLeft),
		new VertexPositionTexture(new Vector3(-1.0f, 1.0f, -1.0f),topLeft),
		new VertexPositionTexture(new Vector3(1.0f, 1.0f, 1.0f),bottomRight),
		new VertexPositionTexture(new Vector3(1.0f, 1.0f, -1.0f),topRight),

		// Bottom Surface
		new VertexPositionTexture(new Vector3(-1.0f, -1.0f, -1.0f),bottomLeft),
		new VertexPositionTexture(new Vector3(-1.0f, -1.0f, 1.0f),topLeft),
		new VertexPositionTexture(new Vector3(1.0f, -1.0f, -1.0f),bottomRight),
		new VertexPositionTexture(new Vector3(1.0f, -1.0f, 1.0f),topRight),
	};

 

If you look at the last argument to the constructor of each of the VertexPositionTexture structures you will see one of four variables; topLeft, bottomLeft, topRight, and bottomRight. These correspond with the four corners of a texture. For those unfamiliar with texture mapping, the goal of texture mapping is to map the image found in a 2D texture onto the surface of a 3D primitive. In order to do this, we assign UV coordinates to each of the vertices of a triangle and then let the interpolator, executed between the vertex shader and the pixel shader, determine the UV coordinates of all the pixels in-between. Unlike world coordinates, or even screen coordinates, texels are represented as (u,v) pairs, in which each coordinate is a percent of the texture from 0.0f to 1.0f. For a visual representation of this, see the image to the right.

Ok, after that sojourn into texture mapping, let us return to the code at hand. Just after the collection of VertexPositionTexture structures there are two small arrays of short integers. These will be the indices for our 3D primitives.

short[] pyramidIndices = new short[] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11 };

short[] boxIndices = new short[] { 
	0, 1, 2, 2, 1, 3,   
	4, 5, 6, 6, 5, 7,
	8, 9, 10, 10, 9, 11, 
	12, 13, 14, 14, 13, 15, 
	16, 17, 18, 18, 17, 19,
	20, 21, 22, 22, 21, 23
};

For those not familiar with indices, consider this: A box has six sides, with two triangles per side. If we were to include each of the vertices for each of the triangles in our triangle list, we would have 36 vertices! If we think about it carefully, many of the vertices (two per side) are duplicates. They have the exact same position and UV coordinates as a vertex on other triangle of the same quad. By using index buffers, we can reduce the number of vertices pushed to the device, and re-use the ones needed by a different triangle.

With all that said, our current vertex structure has only two fields and our buffer is only 36 vertices long. That is not many vertices to send to the video card, nor is it an overly complex format. So why use index buffers? Simple, when you do not use index buffers, the vertex caching mechanism is disabled on most video cards, which forces all the vertices to be re-ran through your shaders. So not only does using index buffers reduce the number of vertices sent to the video card, but it can also reduce the number of vertices that must be shaded.

The next part of the Initialize method is the creation and assignment of the vertex and index buffers for both the pyramid and the box.  For those people coming from a previous version of XNA, you may notice that in XNA 4.0 the VertexBuffer constructor now takes an argument of type VertexDeclaration. This is new, and you’ll see why when we get to the Draw method.

pyramidVB = new VertexBuffer(GraphicsDevice, VertexPositionTexture.VertexDeclaration, 12, BufferUsage.WriteOnly);
	pyramidIB = new IndexBuffer(GraphicsDevice, IndexElementSize.SixteenBits, 12, BufferUsage.WriteOnly);

	pyramidVB.SetData(triangleData);
	pyramidIB.SetData(pyramidIndices);
	triangleData = null;
	pyramidIndices = null;

	boxVB = new VertexBuffer(GraphicsDevice, VertexPositionTexture.VertexDeclaration, 24, BufferUsage.WriteOnly);
	boxIB = new IndexBuffer(GraphicsDevice, IndexElementSize.SixteenBits, 36, BufferUsage.WriteOnly);

	boxVB.SetData(boxData);
	boxIB.SetData(boxIndices);
	boxData = null;
	boxIndices = null;

	base.Initialize();
}

The LoadContent method has a couple new changes in it. First, after the ResetProjection method, which is responsible for computing and setting the projection matrix, we use the ContentManager to load a Texture2D object and set it on the effect. Simply put, this tells the effect which image you want to draw with.

protected override void LoadContent()
{
	Effect tempEffect = Content.Load("Effects/Default");
	effect = new DefaultEffect(tempEffect);
	tempEffect = null;

	ResetProjection();
	effect.Texture = Content.Load("Textures/Crate");
	GraphicsDevice.SamplerStates[0] = new SamplerState()
	{
		Filter = textureFilter
	};
}

If we were to render all primitives perpendicular to the camera, and we were to draw them all at such a distance that there was precisely one texel for each corresponding pixel on the screen, then texture mapping would be simple. However, this is rarely (read never) the case (except in pre-transformed 2D rendering). In reality, 3D primitives are drawn at odd angles, sometimes with only a sliver on the screen. Additionally, objects are constantly moving back and forth, closer and further from the screen. When this happens, the video card and driver must apply a filter to the texture in order to determine how texels will be mapped to pixels.

When an object is positioned or scaled in such a way that multiple pixels on the screen map to a single texel, this is called texture magnification, as it appears as though the textures are being magnified. When a single pixel on the screen maps to multiple texels on the texture it is called texture minification. In previous editions of XNA, it was necessary to set the filtering method for magnification and minification separately. In XNA 4.0, however, both are set with a single property on the SamplerState class called Filter. This property can have any of the values of the TextureFilter enumeration, which as of XNA 4.0 includes:

  • Linear
  • Point
  • Anisotropic
  • LinearMipPoint
  • PointMipLinear
  • MinLinearMagPointMipLinear
  • MinLinearMagPointMipPoint
  • MinPointMagLinearMipLinear
  • MinPointMapLinearMipPoint 

The first three values are straightforward and instruct the device to use Linear, Point, or Anisotropic Filtering when doing minification, magnification, or mipmapping (which I will talk about more in a later tutorial). The latter six are a bit more difficult to read, but are no more complex, and instruct the device which filtering methods to use for each of the different filtering modes. For example, MinLinearMagPointMipLinear tells the device to lerp for both texture minification and mipmapping, but to use nearest-neighbor filtering for magnification.

The next method to look at is the Update method. This project adds several new conditions to the Update method. First, we check to see if any of the following keys were pressed: A, R, F, PageUp, and PageDown. The A key is used to toggle whether or not the shapes are being animated and the R key is used to "reset" the objects back to their original orientations. The PageUp and PageDown keys are used to adjust at what depth the object are being drawn at, with PageUp moving the objects further away, and PageDown moving them closer. Finally, the F key is used to iterate between the different filtering methods so you can see what impact they have on the rendering of our objects.

The last thing the Update method does is, if animating, update the pyramid and box rotation angles so the objects appear to move.

protected override void Update(GameTime gameTime)
{
	if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed)
		this.Exit();

	KeyboardState keyboard = Keyboard.GetState();

	if (keyboard.IsKeyDown(Keys.Escape))
		Exit();

	if (keyboard.IsKeyDown(Keys.F11) && prevKeyboardState.IsKeyUp(Keys.F11))
		IsFullScreen = !IsFullScreen;

	if (keyboard.IsKeyDown(Keys.A) && prevKeyboardState.IsKeyUp(Keys.A))
		isAnimating = !isAnimating;

	if (keyboard.IsKeyDown(Keys.R) && prevKeyboardState.IsKeyUp(Keys.R))
	{
		triangleAngle = 0f;
		rectangleAngle = 0f;
	}

	if (keyboard.IsKeyDown(Keys.F) && prevKeyboardState.IsKeyUp(Keys.F))
	{
		textureFilter = textureFilter + 1;
		if (textureFilter > TextureFilter.MinPointMagLinearMipPoint)
			textureFilter = TextureFilter.Linear;

		GraphicsDevice.SamplerStates[0] = new SamplerState()
		{
			Filter = textureFilter
		};
	}

	if (keyboard.IsKeyDown(Keys.PageUp))
	{
		depth -= 0.05f;
		ResetTranslation();
	}

	if (keyboard.IsKeyDown(Keys.PageDown))
	{
		depth += 0.05f;
		ResetTranslation();
	}

	if (isAnimating)
	{
		triangleAngle += MathHelper.ToRadians(1.0f);
		if (triangleAngle > MathHelper.TwoPi)
			triangleAngle -= MathHelper.TwoPi;

		rectangleAngle -= MathHelper.ToRadians(0.75f);
		if (rectangleAngle < 0.0f)
			rectangleAngle += MathHelper.TwoPi;
	}

	prevKeyboardState = keyboard;
}

The final method we will look at of the Game1 class is the Draw method. While arguably one of the most important methods of our class, it is in many ways the least remarkable as there are relatively few changes to it.

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

		effect.World = Matrix.CreateRotationY(triangleAngle) * triangleTransform;
		effect.CurrentTechnique.Passes[0].Apply();

		GraphicsDevice.SetVertexBuffer(pyramidVB);
		GraphicsDevice.Indices = pyramidIB;
		GraphicsDevice.DrawIndexedPrimitives(PrimitiveType.TriangleList, 0, 0, 5, 0, 4);

		effect.World = Matrix.CreateFromYawPitchRoll(rectangleAngle,
					rectangleAngle, rectangleAngle) * rectangleTransform;
		effect.CurrentTechnique.Passes[0].Apply();

		GraphicsDevice.SetVertexBuffer(boxVB);
		GraphicsDevice.Indices = boxIB;
		GraphicsDevice.DrawIndexedPrimitives(PrimitiveType.TriangleList, 0, 0, 24, 0, 12);
	}
}

First, there are two additional statements before each of our Draw calls. The first is to SetVertexBuffer and the latter is the assignment of our Indices. In previous versions of XNA, the vertex buffer was set similarly to the Indices with the assignment of Vertices[slot], where "slot" identified the stream number you wanted to bind to. In XNA 4.0, however, we use a method call. It is important to note that SetVertexBuffer is overloaded and provides a second version that takes an index as the second parameter, which is used to specify the stream number to bind to.

Another new addition to XNA 4.0 with respect to vertices is the SetVertexBuffers method. SetVertexBuffers takes a VertexBufferBinding[] array, and can be used to set all the vertex streams in a single call. This could be useful if you are serious about performance and are breaking up your streams, or if you are doing hardware instancing and you need to reset multiple streams frequently.

One thing you should notice missing from this method is the assignment of the VertexDeclaration. In previous versions of XNA it was required that you set those manually so the device knew the format of the vertices. In XNA 4.0, however, the vertex declaration is bound to the vertex buffer, so it is set automatically when you call SetVertexBuffer.

The other thing that is new in the Draw method is which Draw method we call on the device. In previous tutorials, we called DrawUserPrimitive, but in this tutorial, since we are using indexed, buffered vertices, we call the corresponding DrawIndexedPrimitives method. This method is far more performant, especially for Draw calls with a larger number of primitives.

The DefaultEffect Class

With the Game1 class out of the way, we can finally turn our attention to the DefaultEffect class. Absent from this class are the shader index and diffuse material color properties which were added in tutorial 3. They are unnecessary in this tutorial because we are only using a single technique, and pulling our diffuse material color from a texture, rather than setting it via an EffectParameter. What is new in this version, however, is the addition of the Texture property. As you can see in the constructor, the EffectParameter for the texture is obtained and cached in the same method as any other shader parameter, and is used by the Texture property to set the texture for the effect. Overall, there is nothing particularly new or exciting here if you have been following along.

class DefaultEffect : Effect, IEffectMatrices
{
	EffectParameter world;
	EffectParameter projection;
	EffectParameter texture;

	public DefaultEffect(Effect effect)
		: base(effect)
	{
		world        = Parameters["World"];
		projection   = Parameters["Projection"];
		texture      = Parameters["DiffuseTexture"];
	}

	public Matrix World
	{
		get { return world.GetValueMatrix(); }
		set { world.SetValue(value); }
	}

	public Matrix View
	{
		get { return Matrix.Identity; }
		set { }
	}

	public Matrix Projection
	{
		get { return projection.GetValueMatrix(); }
		set { projection.SetValue(value); }
	}

	public Texture2D Texture 
	{
		get { return texture.GetValueTexture2D(); }
		set { texture.SetValue(value); }
	}
}

The DefaultEffect.fx File

The last part of this tutorial is the effect file for our default effect. There are two new global variables in this effect file. The first is an object of type Texture2D, which stores the texture object we will sample. The second is an object of type Sampler2D.

float4x4 World;
float4x4 Projection;
Texture2D DiffuseTexture;

sampler2D DiffuseSampler = sampler_state
{
	Texture = ;
};

Samplers are a complex type that can have many different properties in them. However, we use only one in this tutorial, the Texture property, which we set to be a reference to our DiffuseTexture object. The other properties, which can be set in a sampler state, are the same as they are for XNA versions prior to 4.0, as well as DirectX 9. These include:

  • AddressU
  • AddressV
  • AddressW
  • BorderColor
  • Filter
  • MaxAnisotropy
  • MaxLOD
  • MinLOD
  • MipLODBias 

The next part of the effect file is the vertex declaration. Using the same convention as XNA, I named the struct after the elements that make up the vertex format. In this case, I call it VertexPositionTexture, just like that provided by XNA.

struct VertexPositionTexture
{
    float4 Position : POSITION0;
	float2 UV       : TEXCOORD0;
};

Finally, we get to the vertex and pixel shaders. The vertex shader is very similar to the previous shaders. The first line creates an instance of the VertexPositionTexture structure, and is then followed by a simple computation which converts the input vertex position into projection space. The new part of the function is the assignment of the UV coordinates to the output structure. As we discussed in the previous tutorial, anything returned from the vertex shader is interpolated over the triangle and passed on to the pixel shader for further processing. This is precisely what we want, since we need each projected pixel on the screen to sample a texel within the texture.

Looking ahead to the pixel shader you can see that it has only a single line. In the pixel shader, we use the intrinsic function tex2D to sample a 2D texture given a sampler state and a texture coordinate. As our sampler, we use the global variable DiffuseSampler previously discussed. For the texture coordinate, we pass in the interpolated UV values by the interpolator before being passed into the pixel shader.

VertexPositionTexture TexturedVertexShader(VertexPositionTexture input)
{
    VertexPositionTexture output;

    output.Position = mul(input.Position, World);    
    output.Position = mul(output.Position, Projection);
	output.UV       = input.UV;

    return output;
}

float4 TexturedPixelShader(VertexPositionTexture input) : COLOR0
{
    return tex2D(DiffuseSampler, input.UV);
}

technique DefaultEffect
{
    Pass
    {
        VertexShader = compile vs_2_0 TexturedVertexShader();
        PixelShader  = compile ps_2_0 TexturedPixelShader();
    }
}

If you obtain the base code and make all of the previous changes, or, if you download the complete project file located at the bottom of this tutorial and compile/run it, you will see an image that looks similar to the following:

Download Project #4

Comments (15) -

Caesar
Caesar
4/20/2010 10:47:28 PM #

I just read a post on Shawn Hargreaves blog that there have been changes to the way primitives are handled in 4.0.  Your vertex declaration stuff will now be out of date.

Jeromy Walsh
Jeromy Walsh
4/20/2010 11:01:51 PM #

Hi Caesar,

You're right that XNA 4.0 changes the way vertex buffers and declarations interact. However, my tutorial is based on XNA 4.0, and already includes the changes indicated in Shawn's Blog. For example, I mentioned in my tutorial that VertexBuffers are now strongly typed, and are bound directly to VertexDeclarations. In Shawn's blog he used one of the two VertexBuffer constructors. The one he used took the type of the vertex structure as the second argument to the VertexBuffer constructor. I used the second constructor, which accepts a VertexDeclaration.

Additionally, I demonstrated the use of the new SetVertexBuffer method, as well as the lack of need to assign a VertexDeclaration directly on the device as you would have in XNA 3.1. You can be confident that all of my tutorials cover the latest release available for XNA 4.0, compile, and run.

Thanks for reading!

Duque
Duque
5/13/2010 12:48:05 AM #

Hi ... nice tutorials. Only a thing, if you use indices, only need declare 8 vertices in the VertexBuffer of the cube, only if you use DrawUserPrimitives() need specified the 24 vertices, for not shared them.

Jeromy Walsh
Jeromy Walsh
5/14/2010 10:13:48 AM #

Hi Duque,

You could limit it to 8 vertices if each vertex used the same data across all primitives. However, each vertex is used on three different sides, each requiring different UV coordinates. If you re-use the same vertex as part of a different side, the texture mapping would be incorrect.  For example, the top left vertex facing the camera contains the UV coordinates (0,0) for the front quad, but contains the UV coordinates (1,0) for the left facing quad.

Lukasz Iwanski
Lukasz Iwanski
12/1/2010 1:10:15 PM #

Great tutorial!
I was looking for something simple that shows how to start using HLSL in XNA 4.0, to be honest I ma bit disappointed because it is quite hard to find good source of knowledge. if you type XNA 4.0 tutorial. what will show up? tutorials for XNA 3.

Btw. I am in middle of developing one small project and I want to use multi-texture for high map.. I have found great tutorial but there is a trick, for XNA 3.0, but hey "I can do it" attitude told me that "I can do it".. however I have problem with smooth transition between textures,

basically I do not know how to start dealing with that.

I am grateful for any advice,

Regards,
L.

Greg
Greg
12/15/2010 4:17:04 PM #

Pardon my "newbness" with C#, but as I understand it, all properties and methods of a class are private by default. So why declare triangleAngle and rectangleAngle as private? Aren't ALL of the properties going to be private (since none are declared public or protected)?

steve
steve
1/1/2011 1:27:53 PM #

Hello Greg.

It is common practice for many programmers to declare properties and methods as Private, although they are indeed Private by default.

I believe the reason is it can cut down on later problems I.E making assumptions rather than specifically declaring etc etc.

Regards Steve

ChaosVisum
ChaosVisum
12/31/2010 3:10:54 AM #

There is an error in your example code for the Default.fx presented in the tutorial.  The correct code is in the download.

//Tutorial code
sampler2D DiffuseSampler = sampler_state
{
  Texture = ;
};

//Download code
sampler2D DiffuseSampler = sampler_state
{
  Texture = <DiffuseTexture>;
};

Aside from this tutorial I have no shader experience
It would be nice to have the meaning of the syntax explained more

Jeromy Walsh
Jeromy Walsh
12/31/2010 8:56:08 AM #

Chaos,

Thanks for catching that. My blog software removed the DiffuseTexture reference inside of the code example because it's inside of '<' and '>' so it perceives it as HTML.

ED
ED
1/5/2011 4:47:40 AM #

If I wanted to use just a section of my texture (i.e. from a SpriteSheet) how would I do that?

Hurray Banana
Hurray Banana
1/7/2011 1:53:01 AM #

if you want to use a portion of a texture you need to modify the uv co-ordinates that the vertices map to (these are set in the Initialise() method of Game1)

This modification below will show the top left corner of the the texture as I have specified 0.5f as my bottom and right hand side uv values (half way across and half way down the texture). It works nice for the cube in this example, but not so nice for the triangle.

            Vector2 topLeft = new Vector2(0.0f, 0.0f);
            Vector2 topRight = new Vector2(0.5f, 0.0f);
            Vector2 bottomLeft = new Vector2(0.0f, 0.5f);
            Vector2 bottomRight = new Vector2(0.5f, 0.5f);

Obviously it gets a little more complicated when the uv co-ordinates aren't so simple, but organising the sprite sheet sensibly can help with this.

Make sure your spritesheets are powers of 2 in dimension, 256x256 512x512 1024x1024 2048x2048

artpoz
artpoz
1/14/2011 6:08:47 AM #

There is an error in code. Is should be for pyramid:

GraphicsDevice.DrawIndexedPrimitives(PrimitiveType.TriangleList, 0, 0, 12, 0, 4);

Jeromy Walsh
Jeromy Walsh
1/14/2011 9:07:41 AM #

Artpoz,

Good catch. I'm not sure how that got changed. There are indeed 12 vertices in that vertex buffer. More surprisingly however is that it still renders fine when I said there were 5 vertices. What's more, I modified the code and put:

GraphicsDevice.DrawIndexedPrimitives(PrimitiveType.TriangleList, 0, 0, 1, 0, 4);

It seems you can put any non-zero value you like for the number of vertices and it still draws correctly.

Thanks again for catching that!

upd
upd
1/17/2011 8:47:07 PM #

Great tutorial, but one question what if i have two texture's one for pyramid and one for box, how do that ?

Kmost
Kmost
9/18/2011 3:50:53 PM #

Hi, I've got this error

error X3000: syntax error: unexpected token ';'  

in this part of the code in Default.fx

sampler2D DiffuseSampler = sampler_state
{
    Texture = ;
};

Someone knows how to solve it? or have the code for that class?

Pingbacks and trackbacks (1)+

About the author

Jeromy Walsh is a professional game programmer with multiple credited and uncredited AAA game titles. Jeromy's primary area of expertise is in Tools/Engine development, though he likes to fiddle with other areas as well.

Month List