Jump to content
  • Advertisement
Sign in to follow this  
Spa8nky

Texture Atlas And Scrolling Textures.

This topic is 2722 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

If you intended to correct an error in the post then please contact us.

Recommended Posts

Hi folks,

I've been trying to figure this out for a while now and any extra brains on board would be most welcome.

I'm looking for a way to scroll a texture in a texture atlas and have the scrolling wrap around the source rectangle of the texture in question.

This is what I have so far but it doesn't wrap correctly:


VertexShaderOut VertexShaderFunction(VertexShaderIn input)
{
VertexShaderOut Output = (VertexShaderOut)0;

input.Position = mul(input.Position, World);
Output.Position = mul(input.Position, ViewProjection);

input.Position.xy -= HalfPixel;

Output.TextureCoordinates0 = input.TextureCoordinates;
Output.TextureCoordinates1 = input.TextureCoordinates;

// • Adjust uv coordinates so they use the correct texture from the texture atlas
float left = SourceRectangle0.x / TextureAtlas0Width;
float height = SourceRectangle0.w / TextureAtlas0Height;
float top = SourceRectangle0.y / TextureAtlas0Height;
float width = SourceRectangle0.z / TextureAtlas0Width;

Output.TextureCoordinates0.x *= width; // Scale uv coordinates
Output.TextureCoordinates0.x += left; // Shift (rectangle offset from left of atlas)
Output.TextureCoordinates0.y *= height;
Output.TextureCoordinates0.y += top;

// • Animate/Scroll Texture
// • Keep scrolling texture inside current source rectangle
float offset = Time * ScrollSpeed0;
offset %= height;
Output.TextureCoordinates0.y += offset;
Output.TextureCoordinates0.y += height * sign(ScrollSpeed0);

return Output;
}


How can I scroll the texture and keep the uv coordinates wrapped to the source rectangle area?

Share this post


Link to post
Share on other sites
Advertisement
You want your Texture to repeat itself once your outside of the given "SourceRectangle", did I understand that correct?
This is how I would do it, pass the "Upper Boundary" V-Coordinate of your repeating Texture as well as the Height to the Pixel Shader. Then inside the Pixel Shader, check if the current V-Coordinate is larger than the Upper Boundary. If yes, subtract the Height.

Hope this helps smile.gif

Share this post


Link to post
Share on other sites

Hope this helps smile.gif



It most certainly does :D

I have to keep the offset in range of the rectangle using a modulo operator. If there's a better way then I'm all ears:



// • Keep scrolling texture inside current source rectangle
float offset = Time * ScrollSpeed0;
offset %= height; // Keep offset in height range
Output.TextureCoordinates0.y += offset;

Output.Height = height;


Pixel Shader:



float4 PixelShaderFunction(VertexShaderOut input) : COLOR0
{
if (input.TextureCoordinates0.y > input.Height.x)
{
input.TextureCoordinates0.y -= input.Height.x;
}

...............


Now the problem is how to deal with diagonal movement.

Thank you very much.

Share this post


Link to post
Share on other sites
Can explain why I have a tiny offset in my texture when scrolling? It is shown in the picture below:

AnimatedSlither.png



Here is my complete HLSL shader code:



//=============================================
//---[XNA to HLSL Variables]-------------------
//=============================================

shared float2 HalfPixel;
shared texture TextureAtlas0;
shared float TextureAtlas0Height;
shared float TextureAtlas0Width; // Will change to float2 rather than 2 floats
shared float4x4 ViewProjection;

float4 ScrollParameters0; // float4(Direction.x, Direction.y, Speed, TotalTime)
float4 SourceRectangle0; // float4(UpperLeft.x, UpperLeft.y, Width, Height)
float4 SourceRectangle1;
float4x4 World;

//=============================================
//---[Texture Samplers]------------------------
//=============================================

sampler TextureSampler0 = sampler_state
{
Texture = <TextureAtlas0>;

MinFilter = Point;
MagFilter = Point;
MipFilter = Point;

AddressU = Clamp;
AddressV = Clamp;
};

//=============================================
//---[Structs]---------------------------------
//=============================================

struct VertexShaderIn
{
float4 Position : POSITION0;
float2 TextureCoordinates : TEXCOORD0;
};

struct VertexShaderOut
{
float4 Position : POSITION0;
float2 TextureCoordinates0 : TEXCOORD0;
float2 TextureCoordinates1 : TEXCOORD1;
float2 Dimensions : TEXCOORD2;
};

//=============================================
//---[Vertex Shaders]--------------------------
//=============================================

VertexShaderOut VertexShaderFunction(VertexShaderIn input)
{
VertexShaderOut Output = (VertexShaderOut)0;

// [Transformation]
input.Position = mul(input.Position, World);
Output.Position = mul(input.Position, ViewProjection);

// [Texel To Pixel Align]
input.Position.xy -= HalfPixel;

// [uv coordinates]
Output.TextureCoordinates0 = input.TextureCoordinates;
Output.TextureCoordinates1 = input.TextureCoordinates;

// • Adjust uv coordinates so they use the correct texture from the texture atlas
float left = SourceRectangle0.x / TextureAtlas0Width; // floor()
float height = SourceRectangle0.w / TextureAtlas0Height;
float top = (SourceRectangle0.y / TextureAtlas0Height; // floor()
float width = SourceRectangle0.z / TextureAtlas0Width;

Output.Dimensions.x = width;
Output.Dimensions.y = height;

Output.TextureCoordinates0.x *= width; // Scale uv coordinates
Output.TextureCoordinates0.x += left; // Shift (rectangle offset from left of atlas)
Output.TextureCoordinates0.y *= height;
Output.TextureCoordinates0.y += top;

// • Animate/Scroll Texture
float2 offset = ScrollParameters0.xy * ScrollParameters0.z * ScrollParameters0.w;

// • Keep scrolling texture inside current source rectangle
offset.x %= width;
offset.y %= height; // Keep offset in height range

Output.TextureCoordinates0 += offset;

// • Repeat for SourceRectangle1
left = SourceRectangle1.x / TextureAtlas0Width;
height = SourceRectangle1.w / TextureAtlas0Height;
top = SourceRectangle1.y / TextureAtlas0Height;
width = SourceRectangle1.z / TextureAtlas0Width;

Output.TextureCoordinates1.x *= width; // Scale
Output.TextureCoordinates1.x += left; // Shift
Output.TextureCoordinates1.y *= height;
Output.TextureCoordinates1.y += top;

return Output;
}

//=============================================
//---[Pixel Shaders]---------------------------
//=============================================

float4 PixelShaderFunction(VertexShaderOut input) : COLOR0 // Takes input from output of Vertex Shader
{
if (input.TextureCoordinates0.x > input.Dimensions.x)
{
input.TextureCoordinates0.x -= input.Dimensions.x;
}
if (input.TextureCoordinates0.x < 0)
{
input.TextureCoordinates0.x += input.Dimensions.x;
}
if (input.TextureCoordinates0.y > input.Dimensions.y)
{
input.TextureCoordinates0.y -= input.Dimensions.y;
}
if (input.TextureCoordinates0.y < 0)
{
input.TextureCoordinates0.y += input.Dimensions.y;
}

float4 colour0 = tex2D(TextureSampler0, input.TextureCoordinates0);
float4 colour1 = tex2D(TextureSampler0, input.TextureCoordinates1);

// Modulate2X blend
float4 output;
output.rgb = colour0.rgb * colour1.rgb * 2;
output.a = colour0.a * colour1.a;

return output;
}

//=============================================
//------------ [Techniques] -------------------
//=============================================

technique Textured
{
pass Pass0 // Always Start at Pass 0
{
AlphaBlendEnable = true; // Setup For Alpha Blending
ZEnable = true; // Setup For Alpha Blending
ZWriteEnable = true; // Setup For Alpha Blending

//SrcBlend = One; // Additive Blending
//DestBlend = One; // Additive Blending
//BlendOp = Add; // Additive Blending

SrcBlend = SrcAlpha; // Normal Alpha Blending
DestBlend = InvSrcAlpha; // Normal Alpha Blending
BlendOp = Add; // Normal Alpha Blending

VertexShader = compile vs_2_0 VertexShaderFunction(); // Vertex Shader Version
PixelShader = compile ps_2_0 PixelShaderFunction(); // Pixel Shader Version (ps_1_4 = Pixel Shader v1.4)
}
}

Share this post


Link to post
Share on other sites
EDIT: Large texture problem was me limiting the size of the texture atlas and creating a texture larger than the atlas.

Share this post


Link to post
Share on other sites
1) Seems to be a floating point accuracy problem. Something you're bound to face if you're using Atlases with lots and lots of tiny textures. There are just a limited amounts of numbers a computer can represent with floats between 0.0 and 1.0.

2) Most likely not the same Effect as in 1) as this time the effect is a bit too large. Your Texturecoordinates are obviously outside of 0.0 ... 1.0, therefor "Clamping" occurs, as defined in your Sampler state. A quick fix would be to limit it to those bounds. This will however not get rid of your actual problem: Something doesn't get calculated right. Taking a quick look at your VS-Code, I didn't see any logical errors.

Share this post


Link to post
Share on other sites

[color=#1C2837]

2) Most likely not the same Effect as in 1) as this time the effect is a bit too large. Your Texturecoordinates are obviously outside of 0.0 ... 1.0, therefor "Clamping" occurs, as defined in your Sampler state. A quick fix would be to limit it to those bounds. This will however not get rid of your actual problem: Something doesn't get calculated right. Taking a quick look at your VS-Code, I didn't see any logical errors.
[/quote]

That's correct. I edited my previous post before reading this. I made a silly mistake and thought it might be the same problem.

The actual problem is still very much there like you said.

Share this post


Link to post
Share on other sites
I'd suggest taking any texture that scrolls (or otherwise wants to be rendered with UV coordinates outside the 0-1 range) and creating an individual texture for it so you can wrap the texture without a bunch of additional pixel shader work. It'll also get rid of those nasty seams.

The seams occur because of texture filtering. The texture filtering unit sees one texel at a coordinate of 1.0 and the next at 0.0 and thinks that the texture is really far away at that point because of the huge UV gradient, and it pick the smallest mip level. The smallest mip level will be a flat colour and not look right. You can kind of work around that by manually sampling the top mip level with say tex2dlod() (or just not creating your texture with mip maps in the first place) but it's not ideal - the top mip level may not always be the correct one depending on screen resolution and any scaling of the sprite.

Another reasonable workaround is to put two (or more if need be) copies of the texture into the atlas joined together, so that you don't need to mess about with wrapping texture coordinates in the pixel shader.

Share this post


Link to post
Share on other sites

[color="#1C2837"][size="4"]Another reasonable workaround is to put two (or more if need be) copies of the texture into the atlas joined together, so that you don't need to mess about with wrapping texture coordinates in the pixel shader.
[color="#1C2837"][size="4"][color="#000000"]


If I were to put a 1 pixel border around each texture that is the same as the opposite outer pixel edge, would that work?

You can kind of work around that by manually sampling the top mip level with say tex2dlod()


I looked at this page on MSDN but it hasn't made anything clearer. Would I use the tex2dlod() only in these cases?



if (input.TextureCoordinates0.x > input.Dimensions.x)
{
input.TextureCoordinates0.x -= input.Dimensions.x;
}
if (input.TextureCoordinates0.x < 0)
{
input.TextureCoordinates0.x += input.Dimensions.x;
}
if (input.TextureCoordinates0.y > input.Dimensions.y)
{
input.TextureCoordinates0.y -= input.Dimensions.y;
}
if (input.TextureCoordinates0.y < 0)
{
input.TextureCoordinates0.y += input.Dimensions.y;
}

Share this post


Link to post
Share on other sites

If I were to put a 1 pixel border around each texture that is the same as the opposite outer pixel edge, would that work?


No. You want to duplicate the whole thing so that there's no need to wrap the texture coordinates in the pixel shader. That way you avoid having the abrupt coordinate abrupt transition in the middle of the sprite. If you want to scroll in both directions you need four copies of the whole texture.

Using that method you can only get away with less than a whole extra copy if only a small portion of the texture is visible at any one time.


I looked at this page on MSDN but it hasn't made anything clearer. Would I use the tex2dlod() only in these cases?


The important note on that page is "The mipmap LOD is specified in t.w." You want to manually specify that as the top mip map level, which if I remember right means 0.0 . If that doesn't look right try 1.0.

I'd just use it all the time for sprites that need it. You could try only using it along the edges (on both sides of the join) but I have a feeling it still wouldn't look quite right all the time.

Share this post


Link to post
Share on other sites
Sign in to follow this  

  • Advertisement
×

Important Information

By using GameDev.net, you agree to our community Guidelines, Terms of Use, and Privacy Policy.

Participate in the game development conversation and more when you create an account on GameDev.net!

Sign me up!