I've begun making a 2D sprite based Windows game using XNA. I'm not very experienced with it yet but I'm learning. Let me start off with saying that I'm using XNA game studio 3.1, I haven't updated to 4.0 (yet).
What I'm trying to accomplish is to be able to draw all my sprites to a fixed size buffer which is then, at the end of the rendering pass, scaled to the size of the actual backbuffer and then drawn to that. I'm not sure how multiple resolutions are usually supported, but it seemed like an adequate solution to me.
I tried to achieve this by using a RenderTarget2D object to draw all my stuff to, then get the Texture2D from that and draw that to the backbuffer.
My code looks like this:
private RenderTarget2D RenderTarget;
private DepthStencilBuffer DepthStencilBufferRenderTarget;
private DepthStencilBuffer DepthStencilBufferOriginal;
private SpriteBatch SpriteBatch;
protected override void Initialize()
{
base.Initialize();
RenderTarget = new RenderTarget2D(GraphicsDevice, 1920, 1080, 1, SurfaceFormat.Single);
DepthStencilBufferRenderTarget = new DepthStencilBuffer(GraphicsDevice,
1920, 1080, GraphicsDevice.DepthStencilBuffer.Format);
DepthStencilBufferOriginal = GraphicsDevice.DepthStencilBuffer;
SpriteBatch = new SpriteBatch(GraphicsDevice);
}
protected override void Draw(GameTime gameTime)
{
GraphicsDevice.DepthStencilBuffer = DepthStencilBufferRenderTarget;
GraphicsDevice.SetRenderTarget(0, RenderTarget);
GraphicsDevice.Clear(Color.Black);
SpriteBatch.Begin();
//drawing all stuff here
SpriteBatch.End();
GraphicsDevice.DepthStencilBuffer = DepthStencilBufferOriginal;
GraphicsDevice.SetRenderTarget(0, null);
GraphicsDevice.Clear(Color.Black);
Texture2D output = RenderTarget.GetTexture();
SpriteBatch.Begin();
Rectangle backbuffer = new Rectangle(0, 0, m_Options.DisplayWidth, m_Options.DisplayHeight);
SpriteBatch.Draw(output, backbuffer, Color.White);
SpriteBatch.End();
base.Draw(gameTime);
}
The problem I encounter is that the colours are all wrong. Below is a picture that shows two screenshots: the first is a screenshot of how it's supposed to look (I wrote my own scaling algorithm before which simply scaled each sprite by itself) and to the right it's how it looks when using the RenderTarget2D.
Does anyone know what I'm doing wrong?
Oh I figured out what I did wrong (kind of). I think I used the wrong SurfaceFormat when creating the new RenderTarget2D instance. I used SurfaceFormat.Single but I should have used SurfaceFormat.Color.
I figured this out by taking a look at the PresentationParameters of the GraphicsDevice through a watch while debugging before setting the new RenderTarget:
GraphicsDevice.PresentationParameters.BackBufferFormat
This told me it was set to SurfaceFormat.Color. So I replaced SurfaceFormat.Single with SurfaceFormat.Color and now it works as it should.
Related
(Cross post from Gamedevs)
I'm working with Monogame to do a 2d sprite engine. I've gotten it work pretty well where I can move lots of sprites around the screen with the effects and positions that I want.
Last night I tried to add a feature and I can't quite get it to work right.
I'm trying to create permanent trails that the moving sprites can leave behind. So I have a mostly transparent PNG with some white lines on it called "streak". The idea is that I set up a new Texture2D surface (called streakoverlay) and I draw the streaks on to it.
I switch back to the back buffer and draw: The background, the streakoverlay, and finally the sprite on top of it. The streaks should build up over time. It almost works, but the trouble I have is that streakOverlay seems to clear itself every time. How I'd like it to behave would be the same behaviour as having the graphics display without this line GraphicsDevice.Clear(Color.White); - everything would just pile up in it.
Instead it resets back to the Purple transparent color that is the default for that texture. Any suggestions? Here's the core piece of the rendering engine:
GraphicsDeviceManager graphics;
RenderTarget2D streakOverlay;
SpriteBatch spriteBatch;
private Texture2D bkg;
private Texture2D prototype;
private Texture2D streak;
//Initialize
graphics = new GraphicsDeviceManager(this);
GraphicsDevice.Clear(Color.Transparent);
streakOverlay = new RenderTarget2D(GraphicsDevice, 200,200);
//Load Content
bkg = Content.Load<Texture2D>("bkg"); //Background image
prototype = Content.Load<Texture2D>("prototype"); //Prototype sprite that moves around
streak = Content.Load<Texture2D>("blueStreak"); //Trails being left by Prototype sprite
graphics.GraphicsDevice.SetRenderTarget(streakOverlay);
GraphicsDevice.Clear(Color.Transparent); //Attempt to make the streakoverlay is fully transparent.
graphics.GraphicsDevice.SetRenderTarget(null);
//Draw
Random r = new Random();
graphics.GraphicsDevice.SetRenderTarget(streakOverlay); //Switch to drawing to the streakoverlay
spriteBatch.Begin();
//Draw some streaks that should accumulate
spriteBatch.Draw(streak, new Vector2(r.Next(0, 200), r.Next(0, 200)), null, Color.White, 0f, new Vector2(25, 25), 1f, SpriteEffects.None, 1f);
spriteBatch.End();
//Switch back to drawing on the back buffer.
graphics.GraphicsDevice.SetRenderTarget(null);
spriteBatch.Begin();
spriteBatch.Draw(bkg, new Rectangle(0, 0, 2000, 2000), Color.White); //Draw our background
spriteBatch.Draw(streakOverlay, new Vector2(0, 0), Color.White); //Put the overlay on top of it
spriteBatch.Draw(prototype, new Vector2(_spritePrototype.getX, _spritePrototype.getY), null, Color.White, DegreeToRadian(rotationSprite), new Vector2(25, 25), .5f, SpriteEffects.None, 0f); //Draw the sprite that's moving around.
spriteBatch.End();
base.Draw(gameTime);
You need to use the extended constructor for RenderTarget2D that takes 4 parameters and specify PreserveContents. The assumption is that render targets are often reused and so when it is set it gets cleared automatically unless this flag is set.
Very odd problem, when I try and draw my billboard sprite it always appears as a white block, changing the .draw color property still draws it as white, it also doesn't matter it I use a jpeg, or transparent png.
[EDIT]
So I'm now trying to use a Viewport instead of a basic effect to just get an x and y screen coordinate, I'll fix any scaling issue later, however now the image stays in the exact same spot (on the screen, it doesn't change position based on the camera) and doesn't get any bigger or smaller based on how far away it is
My new billboard rendering function:
public void Draw(Camera camera, GraphicsDevice device, SpriteBatch spriteBatch, Texture2D Texture)
{
Viewport viewport = new Viewport(new Rectangle(0, 0, 800, 480));
Vector3 viewSpaceTextPosition = viewport.Project(this.position, camera.Projection, camera.View, camera.World);
spriteBatch.Begin();
spriteBatch.Draw(Texture, new Vector2(viewSpaceTextPosition.X, viewSpaceTextPosition.Y), null, Color.White, 0, new Vector2(Texture.Bounds.Center.X, Texture.Bounds.Center.Y), this.Scale, SpriteEffects.None, viewSpaceTextPosition.Z);
spriteBatch.End();
device.RasterizerState = RasterizerState.CullCounterClockwise;
device.BlendState = BlendState.Opaque;
device.DepthStencilState = DepthStencilState.Default;
}
So is my use of Viewport wrong or do I just need to use it's information differently in spriteBatch.Draw()?
I think this should do the trick using viewport project... taking two projection points and calculating its distance you get a value affected by depth... so if it's deeper that value will be smaller.
public void Draw(Camera camera, GraphicsDevice device, SpriteBatch spriteBatch, Texture2D Texture)
{
Vector3 pos1= device.Viewport.Project(
this.position,
camera.Projection, camera.View, camera.World);
Vector3 pos2= device.Viewport.Project(
this.position+ Vactor3.UnitY*10,
camera.Projection, camera.View, camera.World);
Vector2 pos = new Vector2(pos1.X, pos1.Y);
Vector2 origin = new Vector2(Texture.Bounds.Center.X, Texture.Bounds.Center.Y);
float Scale = Vector3.Distance(pos1, pos2) * CustomRatio;
spriteBatch.Begin();
spriteBatch.Draw(Texture, pos, null, Color.White, 0,
origin, Scale, SpriteEffects.None, 0);
spriteBatch.End();
device.RasterizerState = RasterizerState.CullCounterClockwise;
device.BlendState = BlendState.Opaque;
device.DepthStencilState = DepthStencilState.Default;
}
In other hand... your previous code seems to be extracted from a source that drinks from this article made by the guy behind Xna that explain how to use basiceffect to draw billboards in 3D with spritebatch...
http://blogs.msdn.com/b/shawnhar/archive/2011/01/12/spritebatch-billboards-in-a-3d-world.aspx
I hope it helps you
Figured it out, you have to enable textures on the effect and then set effect.Texture to the Texture2D you want to use just before calling spriteBatch.Begin() in the draw function
I have a windows platform game coded in C# XNA 4.0 using the Reach graphics settings. My project is based on the GameStateManagement sample but I later added Bloom and spriteSheet/spriteBatch functionality to it.
I desire to have a screenshot saved of the final screen output. However, when I save my screenshot it only shows the render before Bloom was applied and before my HUD text is displayed (which I draw after the Bloom). I have my screenshot saved at the end of my Draw method, after these two processes.
I have tried all kinds of things. Andrew's answer here Take screen shot in XNA was helpful and does save out an image; however, it is not saving out the final render.
I have a feeling it has something to do with the bloom process or maybe the spritebatch.
Here is my code:
example {
public override void Draw(GameTime gameTime)
{
ScreenManager.GraphicsDevice.SetRenderTarget(sceneRenderTarget);
// Clear the screen to black
ScreenManager.GraphicsDevice.Clear(ClearOptions.Target,
Color.Black, 0, 0);
SpriteBatch spriteBatch = ScreenManager.SpriteBatch;
spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.AlphaBlend);
// then i draw all my game stuff
spriteBatch.End();
#region Post-Processing & Bloom
ScreenManager.GraphicsDevice.SamplerStates[1] = SamplerState.LinearClamp;
// Pass 1: draw the scene into rendertarget 1, using a
// shader that extracts only the brightest parts of the image.
bloomExtractEffect.Parameters["BloomThreshold"].SetValue(
Settings.BloomThreshold);
DrawFullscreenQuad(sceneRenderTarget, renderTarget1,
bloomExtractEffect,
IntermediateBuffer.PreBloom);
// Pass 2: draw from rendertarget 1 into rendertarget 2,
// using a shader to apply a horizontal gaussian blur filter.
SetBlurEffectParameters(1.0f / (float)renderTarget1.Width, 0);
DrawFullscreenQuad(renderTarget1, renderTarget2,
gaussianBlurEffect,
IntermediateBuffer.BlurredHorizontally);
// Pass 3: draw from rendertarget 2 back into rendertarget 1,
// using a shader to apply a vertical gaussian blur filter.
SetBlurEffectParameters(0, 1.0f / (float)renderTarget1.Height);
DrawFullscreenQuad(renderTarget2, renderTarget1,
gaussianBlurEffect,
IntermediateBuffer.BlurredBothWays);
// Pass 4: draw both rendertarget 1 and the original scene
// image back into the main backbuffer, using a shader that
// combines them to produce the final bloomed result.
ScreenManager.GraphicsDevice.SetRenderTarget(null);
EffectParameterCollection parameters = bloomCombineEffect.Parameters;
parameters["BloomIntensity"].SetValue(Settings.BloomIntensity);
parameters["BaseIntensity"].SetValue(Settings.BaseIntensity);
parameters["BloomSaturation"].SetValue(Settings.BloomSaturation);
parameters["BaseSaturation"].SetValue(Settings.BaseSaturation);
ScreenManager.GraphicsDevice.Textures[1] = sceneRenderTarget;
Viewport viewport = ScreenManager.GraphicsDevice.Viewport;
DrawFullscreenQuad(renderTarget1,
viewport.Width, viewport.Height,
bloomCombineEffect,
IntermediateBuffer.FinalResult);
#endregion
spriteBatch.Begin();
// Draw HUD
spriteBatch.End();
if (screenShotTake)
{
using (FileStream fs = File.Open(#"screenshot" + (screenshotNumber) + #".png", FileMode.OpenOrCreate))
{
// Right here I try to save out the screen shot Texture2D
sceneRenderTarget.SaveAsPng(fs, (int)viewportSize.X, (int)viewportSize.Y); // save render target to disk
}
}
}
#region PostProcess & Bloom
void DrawFullscreenQuad(Texture2D texture, RenderTarget2D renderTarget,
Effect effect, IntermediateBuffer currentBuffer)
{
ScreenManager.GraphicsDevice.SetRenderTarget(renderTarget);
DrawFullscreenQuad(texture,
renderTarget.Width, renderTarget.Height,
effect, currentBuffer);
}
void DrawFullscreenQuad(Texture2D texture, int width, int height,
Effect effect, IntermediateBuffer currentBuffer)
{
if (showBuffer < currentBuffer)
{
effect = null;
}
spriteBatch.Begin(0, BlendState.Opaque, null, null, null, effect);
spriteBatch.Draw(texture, new Rectangle(0, 0, width, height), Color.White);
spriteBatch.End();
}
}
The problem is here:
ScreenManager.GraphicsDevice.SetRenderTarget(null);
Because of this line, you draw the effects to the back buffer, but the render target you're saving is left alone. You see the results of the effects because it's drawn directly, but what you save is not what you've drawn to the back buffer. To fix this, draw the effects to another RenderTarget, and then draw that as a single texture to the back buffer. Obviously this is another draw call to process, but it's a minimal cost. By doing that, you can then grab the texture from the new RenderTarget and save it however you like.
I am trying to draw text on to the screen using a spritefont in XNA.
I can get the text to appear, but no matter what I try there is always something going wrong with something else when I draw the text.
I am trying to display the FPS.
I have tried it in a few different ways and in various different XNA projects I have made.
Some of the things that happen when drawing the text to the screen include -
Vertices being drawn further away and in the wrong spots, vertices not being drawn at all, wire frame mode not being able to be turned on.
Just depending on how I try to render the text I always end up with one of these.
And then if I comment out the part that draws the text, everything will be back to normal.
Here is some code
My variables at the top of the code are -
GraphicsDeviceManager graphics;
SpriteFont font;
SpriteBatch spriteBatch;
In my LoadContent I have
font = Content.Load<SpriteFont>("SpriteFont1");
RasterizerState rs = new RasterizerState();
rs.FillMode = FillMode.WireFrame;
GraphicsDevice.RasterizerState = rs;
spriteBatch = new SpriteBatch(GraphicsDevice);
And here is my entire Draw method -
protected override void Draw(GameTime gameTime)
{
CreateMesh();
GraphicsDevice.Clear(Color.SkyBlue);
effect.Parameters["View"].SetValue(cam.viewMatrix);
effect.Parameters["Projection"].SetValue(projectionMatrix);
effect.CurrentTechnique = effect.Techniques["Technique1"];
effect.CurrentTechnique.Passes[0].Apply();
GraphicsDevice.DrawIndexedPrimitives(PrimitiveType.TriangleList, 0, 0, vertices.Count, 0, indices.Length / 3);
spriteBatch.Begin();
spriteBatch.DrawString(font, frameRate.ToString(), new Vector2(1, 1), Color.Black);
spriteBatch.End();
frameCounter++;
base.Draw(gameTime);
}
This is the closest way I have foundto working yet, but for some reason it makes wireframe not work no matter what I try to do, and I really need wire frame.
EDIT:
I debugged the fill mode as the program runs and it sets it back to solid on its own for some reason when using the sprite font.
I can reset the the fill mode every frame before the vertices are drawn, but I'd really rather not have to do that.
You should apply your rasterizer state just before drawing somthing win wireframe mode, not only in the load method... because batch.Begin() method will set a default rasterizer state
if you want use other rasterizer state for spritebatch, you should provide one
spritebatch.Begin(SortMode,...,,.., rs );
your code should be changed this way:
static RasterizerState rs = new RasterizerState() { FillMode = FillMode.Wireframe; }
protected override void Draw(GameTime gameTime)
{
CreateMesh();
GraphicsDevice.Clear(Color.SkyBlue);
GraphicsDevice.RasterizerState = rs;
effect.Parameters["View"].SetValue(cam.viewMatrix);
effect.Parameters["Projection"].SetValue(projectionMatrix);
effect.CurrentTechnique = effect.Techniques["Technique1"];
effect.CurrentTechnique.Passes[0].Apply();
GraphicsDevice.DrawIndexedPrimitives(PrimitiveType.TriangleList, 0, 0, vertices.Count, 0, indices.Length / 3);
....
Using a SpriteBatch changes the render state to the point that 3D graphics no longer work unless you change the render state back to a 3D-friendly state. See this article for more details.
http://blogs.msdn.com/b/shawnhar/archive/2006/11/13/spritebatch-and-renderstates.aspx
I'm trying to draw on the screen pixel-by-pixel using XNA, but am having problems with resources. I thought the best way would be to have 1 texture that updates every frame, but I'm having trouble updating it. Here's what I've got so far, just as a test:
Texture2D canvas;
Rectangle tracedSize;
UInt32[] pixels;
protected override void Initialize()
{
tracedSize = GraphicsDevice.PresentationParameters.Bounds;
canvas = new Texture2D(GraphicsDevice, tracedSize.Width, tracedSize.Height, false, SurfaceFormat.Color);
pixels = new UInt32[tracedSize.Width * tracedSize.Height];
base.Initialize();
}
protected override void Draw(GameTime gameTime)
{
GraphicsDevice.Clear(Color.CornflowerBlue);
pixels[100] = 0xFF00FF00;
canvas.SetData<UInt32>(pixels, 0, tracedSize.Width * tracedSize.Height);
spriteBatch.Begin();
spriteBatch.Draw(canvas, new Rectangle(0, 0, tracedSize.Width, tracedSize.Height), Color.White);
spriteBatch.End();
base.Draw(gameTime);
}
When Draw() is called the second time, I get the following error:
"The operation was aborted. You may not modify a resource that has been set on a device, or after it has been used within a tiling bracket."
If I try to make a new Texture2D in Draw(), I quickly get an out of memory error. This is for Windows Phone. It seems like I'm trying to do it the wrong way, what other options do I have to make it work?
Try setting GraphicsDevice.Textures[0] = null before you call SetData. Depending on the effect you're after there may be a more performant method, you could also consider Silverlights WriteableBitmap.
Edit: This is the code I tested in the emulator:
Texture2D canvas;
Rectangle tracedSize;
UInt32[] pixels;
protected override void Initialize()
{
tracedSize = GraphicsDevice.PresentationParameters.Bounds;
canvas = new Texture2D(GraphicsDevice, tracedSize.Width, tracedSize.Height, false, SurfaceFormat.Color);
pixels = new UInt32[tracedSize.Width * tracedSize.Height];
base.Initialize();
}
Random rnd = new Random();
protected override void Draw(GameTime gameTime)
{
GraphicsDevice.Clear(Color.CornflowerBlue);
GraphicsDevice.Textures[0] = null;
pixels[rnd.Next(pixels.Length)] = 0xFF00FF00;
canvas.SetData<UInt32>(pixels, 0, tracedSize.Width * tracedSize.Height);
spriteBatch.Begin();
spriteBatch.Draw(canvas, new Rectangle(0, 0, tracedSize.Width, tracedSize.Height), Color.White);
spriteBatch.End();
base.Draw(gameTime);
}
You basically need to do as it asks in the exception:
To ensure that the texture is not set on the graphics device, put this at the end of Draw:
GraphicsDevice.Textures[0] = null;
To ensure you are not drawing inside a tiling bracket, do not use SetData inside of Draw at all. Move the call to SetData into Update.
Bonus info: Your out-of-memory error is because you are not releasing the unmanaged resources that Texture2D allocates (the garbage collector can't track them, so it doesn't know you're running out of memory). You need to call Dispose on the texture. However making a new texture each frame is a bad idea anyway (there's no way to avoid the performance and memory fragmentation problems it causes).
Never create or modify a texture in Draw() ** ever.**
The SpriteBatch.Draw() call expects the GraphicsDevice buffer to contain all of the texture data (by extension to the GPU buffer), any changes still happening(from Update and IsRunningSlowly == true) at this point will cause tearing when rendered.
The workaround of GraphicsDevice.Textures[0] = null; blocks the Draw call and the game until transfer to the GPU is complete, thus slowing the entire game loop.
Reuse a Texure2D object declared at the class level.