"Wrap-around" effect with a Direct3D.Texture - c#

Given a destination rectangle and an x/y offset value, I need an image to be drawn within the confines of that destination rectangle. If the offset would push the image off the edge of the rectangle, then the part that "pushes out" should appear on the opposite side of the destination rectangle. In simplest terms, I need a scrolling background.
In GDI, I can accomplish this with an "ImageAttributes" object that uses a tile wrap mode:
ImageAttributes attributes = new ImageAttributes();
attributes.SetWrapMode(System.Drawing.Drawing2D.WrapMode.Tile);
Rectangle rectangle = new Rectangle(0, 0, (int)width, (int)height);
g.DrawImage(bmp, rectangle, -x, -y, width, height, GraphicsUnit.Pixel, attributes);
Now, I need a way to do this in DirectX. Assume that this is the method I have right now:
public void RenderTexture(PrismDXObject obj, D3D.Texture texture, int xOffset, int yOffset)
{
if (obj != null && texture != null)
{
_renderSprite.Begin(D3D.SpriteFlags.AlphaBlend);
_renderSprite.Draw(texture,
new Rectangle(0, 0, (int)obj.Width, (int)obj.Height),
new Vector3(0.0f, 0.0f, 0.0f),
new Vector3((int)obj.Left, (int)obj.Top, 0.0f),
obj.RenderColor);
_renderSprite.End();
}
}
}
...where "_renderSprite" is a D3D.Sprite, and PrismDXObject is a simple class that stores x/y/width/height/color. How can I update this method so that xOffset and yOffset can be used to make the texture wrap? Remember, my end-goal is a scrolling background that loops as the player walks forward.
Incidentally, that RenderTexture() method is meant to be a "library method" which can be called from anywhere in my program... so if I'm doing something really inefficient or ill-advised, I'd welcome a friendly warning! My main concern is getting the wrapping background to work, though.

I'm not sure that the sprite mechanism allows for what I'm about to explain, but 2 triangles certainly do. If this does not work with sprites, use triangles directly:
What you're asking for is directly supported by the texturing subsystem, it is called texture wrapping.
When you specify the texture coordinates that your quad will use, instead of using the 0,0-1,1 range, you can use 0+xoffset/tex_x_size, 0+yoffset/tex_y_size, 1+xoffset/tex_x_size, 1+yoffset/tex_y_size for your texture coordinates.
Then, the only thing left to do is to specify that the texture sampler you will use to map your background does texture wrapping. To do this, you need to set to D3DTADDRESS_WRAP the D3DSAMP_ADDRESSU and D3DSAMP_ADDRESSV sampler states. Note, this is the default for the sampler state.
that's it. Now, getting back to D3D.Sprite specifically, the Draw method takes a rectangle that tells which part of the texture to use. have you tried drawing xoffset, yoffset, xOffset+obj,Width, yoffset+obj.height ? This will only work if the sprite subsystem uses a sampler that has wrapping on, and I don't know how sprite is implemented internally.

Related

Monogame: Render only inside specified area

This may be a strange question, but I'm trying to find a way to render sprites only inside a specific allowed area rather then the entire buffer/texture.
Like so:
Basically allowing me to draw to the buffer or texture2D as I normally would, but with actual drawing happening only inside this specified area and remaining pixels outside of it remaining untouched.
Why this is needed - I'm building my own UI system and I would like to avoid using intermediary buffers as it is quite slow when there are many UI components on the screen (and each has to draw to their own buffer to prevent child elements being drawn outside of parent bounds).
And just to clarify - this is all for simple 2D rendering, not 3D.
If your UI is actually drawn with SpriteBatch you can use ScissorRectangle
GraphicsDevice.RasterizerState.ScissorTestEnable = true;
spriteBatch.GraphicsDevice.ScissorRectangle = ...
In 3D, you can render to a texture and draw just a portion of it - or with a shader (you could actually just send in the dimensions as parameter and set it to black in PixelShader if the Pixel is outside that Rectangle (or whatever you want to accomplish)
You can use:
spriteBatch.Draw(yourTexture,
//where and the size of what you want to draw on screen
//for example, new Rectangle(100, 100, 50, 50)//position and width, height
destinationRectangle,
//the area you want to draw from the original texture
//for example, new Rectangle(0, 0, 50, 50)//position and width, height
sourceRectangle,
Color.White);
Then it will only draw the area that you chose before. Hope this helps!

How do I scale the graphics of a game?

I'm making a game in C# and XNA 4.0. It's pretty much finished, but now I want to add settings so that players can change the window size if they want to. The current setup goes like this:
void Initialize()
{
//The window size is initally 800x480
graphics.PreferredBackBufferWidth = 800;
graphics.PreferredBackBufferHeight = 480;
graphics.ApplyChanges();
}
void Update()
{
//If the player completes an action, the window size is changed
if (windowSizeChanged)
{
graphics.PreferredBackBufferWidth = 1024;
graphics.PreferredBackBufferHeight = 720;
graphics.ApplyChanges();
}
}
Using this code, this is what the game looks like at specific resolutions:
800x480
1024x720
As you can hopefully see, when the window size is changed it does not affect the actual graphics of the game. The sprites and hitboxes of all of the objects stay the same size, so they instead fill up a small box in the corner of the screen rather than the entire window. Can anyone tell me how I can scale the sprites so that they fill up the window? I assume I would need to use a matrix of some sort, but can anyone point me in the right direction?
Edit:
Here's the draw code.
void Draw(GameTime gameTime)
{
GraphicsDevice.Clear(Color.CornflowerBlue);
base.Draw(gameTime);
spriteBatch.Begin();
//Background texture drawn at default window size
spriteBatch.Draw(background, new Rectangle(0, 0, 800, 480), Color.White);
//Each object in the level (player, block, etc.) is drawn with a specific texture and a rectangle variable specifying the size and coordinates
//E.g. Each block is a size of 64x64 pixels and in this level they are placed at X-coordinates 0, 64, 128 and so on
//Each Y-coordinate for the blocks in this level is '480 - 64' (Window height minus block height)
foreach (/*Object in level*/)
{
spriteBatch.Draw(object.Texture, object.TextureSize, Color.White);
}
spriteBatch.End();
}
By default, SpriteBatch assumes that your world space is the same as client space, which is the size of the window. You can read about SpriteBatch and different spaces in a post by Andrew Russell.
When you are resizing the backbuffer, the window size will also change changing the world space together with it (which you don't want). In order not to allow that, you should stick a transformation matrix in between the transformation pipeline to make that correction.
SpriteBatch.Begin allows exactly that in one of its overloads.
There are numerous ways to approach the scaling, but I assume that you want to scale uniformly, meaning that sprites don't get stretched out when the aspect ratio changes compared to the initial aspect ratio. The following code will adjust the scaling based on initial screen height.
...
const float initialScreenHeight = 480f;
Matrix transform = Matrix.CreateScale(GraphicsDevice.Viewport.Height / viewportHeightInWorldSpace);
spriteBatch.Begin(SpriteSortMode.Deferred, null, null, null, null, null, transform);
...
Note that when changing the resolution such that the aspect ratio changes compared to the initial aspect ratio, you will run into issues such as drawing out of screen (to the right) or not drawing at the right edge of the screen (getting a similar blue background as currently).
Also, you don't want to calculate that scaling matrix every frame in the Draw method, but only when the resolution is changed.

Resizing a Texture2D without Draw

As the title states, I'm trying to resize a Texture2D before even considering SpriteBatch.Draw(). The reason I'm doing this is I'm trying to fill an arbitrary polygon, laid out with vertices defined by Vector2Ds, with an arbitrary Texture2D.
What I'm thinking of is creating the rectangle that fits the polygon, scaling the Texture2D to that rectangle, and then making the pixels that are outside of the polygon transparent via Texture2D's GetData<>() and SetData<>().
I've gotten to the point of finding the rectangle that fits the polygon, but is there a way to resize the Texture2D, or am I going about it the completely wrong way? Thanks!
You're going about it the wrong way. Setting texture data is expensive. (And there's probably some issues with filtering, too.)
What you want to do is set the texture coordinates (the "UV coordinates") of the vertices you are drawing. This will cause a specific location of your texture to appear at that vertex of your polygon. The texture that would then fall outside your polygon is simply never drawn (it is "clipped" by the polygon edges).
Texture coordinates are specified in the range 0.0 to 1.0 (on the U and V axies - horizontally and vertically) from the top left to the bottom right of your texture.
If you are drawing using vertex buffers, XNA includes some built-in vertex structures like VertexPositionTexture and VertexPositionColorTexture that allow you to specify a TextureCoordinate value.
If you are using your own vertex structure, use VertexElementUsage.TextureCoordinate when specifying a VertexElement. If you are creating your own shader, the value will be exposed in TEXCOORD0 (for usage index 0).
If you are just drawing rectangles with SpriteBatch, then specify a sourceRectangle when you call Draw.
Sounds like you should be using the overloads on the Draw method (I realise you are for some reason not wanting to do this, but it's like this for a good reason):
public void Draw (
Texture2D texture,
Vector2 position,
Nullable<Rectangle> sourceRectangle,
Color color,
float rotation,
Vector2 origin,
Vector2 scale,
SpriteEffects effects,
float layerDepth
)
The sourceRectangle, scale parameter, and origin should be enough. Don't modify the texture in memory, it's relatively expensive to do this (especially doing it every frame!)
http://msdn.microsoft.com/en-us/library/bb196420(v=xnagamestudio.31).aspx
Can you explain why you don't want to scale in Draw()?

Sprite.Draw() draws my textures too small

I declared a device + sprite in a Windows.Form like this
PresentParameters presentParameters = new PresentParameters();
presentParameters.Windowed = true;
presentParameters.SwapEffect = SwapEffect.Copy;
var device = new Device(Manager.Adapters.Default.Adapter, DeviceType.Hardware, this, CreateFlags.HardwareVertexProcessing, presentParameters);
var sprite = new Sprite(device);
I loaded a texture via TextureLoader.FromFile(device, "image.png");
In my Draw method i startet the device scene, then the sprite scene, then i wrote
sprite.Draw2D(texture, PointF.Empty, 0, PointF.Empty, Color.White);
the drawing itself works, but it draws only a big portion of the image scaled up to the screen (like 90%)
i tried it with a source rectangle with the given texture size too, but the same bug occurred
any suggestions?
I am experienced in C++ DirectX, but not C# DirectX, so take this with a grain of salt.
In my experiences with the Sprite interface, you need to scale, rotate, and translate just like you need to with 3D objects. You may be forgetting to scale. Here is the code of my Update function.
void Button::Update()
{
Sprite->Begin(D3DXSPRITE_ALPHABLEND);
D3DXMATRIX trans;
D3DXMATRIX scale;
D3DXMATRIX world;
D3DXMatrixIdentity(&world);
D3DXMatrixTranslation(&trans, pos.x, pos.y, 0.0f);
D3DXMatrixScaling(&scale, scaleFactor, scaleFactor, 1.0f);
world = scale * trans;
Sprite->SetTransform(&world);
Sprite->Draw(buttonTexture, NULL, NULL, &D3DXVECTOR3(-width2, -height2, 0.0), whitecol);
Sprite->End();
}
Admittedly, this isn't a very object-oriented way of doing things, but it suits my needs.
Caveat: I am not an DirectX expert, but I had the same problem.
When you load the sprite it expands the sprite to fit a size where each dimension is a power of 2. For example, If you sprite was 200 x 65, the sprite will have a width of 256 (and the image will be expanded to a width of 256, increasing it slightly) by 128 (almost doubling the height).
When you draw the image, it will be almost twice the height you expected.
My solution was to modify my image file to have a height and width of a factor of 2 and then only draw the portion that was the original size.

Why might Graphics.RotateTransform() not be applied?

I have the following function:
static private Image CropRotate(Image wholeImage, Rectangle cropArea)
{
Bitmap cropped = new Bitmap(cropArea.Width, cropArea.Height);
using(Graphics g = Graphics.FromImage(cropped))
{
g.DrawImage(wholeImage, new Rectangle(0, 0, cropArea.Width, cropArea.Height), cropArea, GraphicsUnit.Pixel);
g.RotateTransform(180f);
}
return cropped as Image;
}
It's supposed to crop an image, then rotate the resulting sub-image. In actuality though, it only performs the crop.
Why is RotateTransform() not being applied?
Have you tried putting the RotateTransform() before the DrawImage()?
The example on the msdn page shows the transformation being applied before any drawing is done.
The RotateTransform call alters the current transform matrix, which has an effect on all subsequent operations. It does not transform the already output operations at all. This is the same for any of the operations that change the transform matrix (like ScaleTransform).
Make sure you call these before you perform the operations you want transformed - in this case, before the call to DrawImage.
You can use this to do something like
Draw (not rotated or scaled)
Rotate (only changes transform matrix)
Scale (only changes transform matrix)
Draw (now rotated and scaled)
ClearTransform (only changes transform matrix)
Draw (not rotated or scaled)
the first and last draw outputs will not be transformed, but the middle one would be affected by both the rotate and scale (in that order).

Categories