How to display a model on top of textures? [XNA]

Started by
13 comments, last by kauna 12 years, 5 months ago
I'm trying to render a 3D mesh above a screen that consists of textures. Based on my research I figured I'd need a rendertarget and something called a "depth stencil buffer".
So I found some code on MSDN for creating these;

private RenderTarget2D CreateRenderTarget(GraphicsDevice device, int numberLevels, SurfaceFormat surface)
{
MultiSampleType type = device.PresentationParameters.MultiSampleType;

// If the card can't use the surface format
if (!GraphicsAdapter.DefaultAdapter.CheckDeviceFormat(
DeviceType.Hardware,
GraphicsAdapter.DefaultAdapter.CurrentDisplayMode.Format,
TextureUsage.None,
QueryUsages.None,
ResourceType.RenderTarget,
surface))
{
// Fall back to current display format
surface = device.DisplayMode.Format;
}
// Or it can't accept that surface format
// with the current AA settings
else if (!GraphicsAdapter.DefaultAdapter.CheckDeviceMultiSampleType(
DeviceType.Hardware, surface,
device.PresentationParameters.IsFullScreen, type))
{
// Fall back to no antialiasing
type = MultiSampleType.None;
}

int width, height;

// See if we can use our buffer size as our texture
CheckTextureSize(device.PresentationParameters.BackBufferWidth,
device.PresentationParameters.BackBufferHeight,
out width, out height);

// Create our render target
return new RenderTarget2D(device,
width, height, numberLevels, surface,
type, 0);
}


private bool CheckTextureSize(int width, int height, out int newwidth, out int newheight)
{
bool retval = false;

GraphicsDeviceCapabilities Caps;
Caps = GraphicsAdapter.DefaultAdapter.GetCapabilities(
DeviceType.Hardware);

// Check if Device requires Power2 textures
if (Caps.TextureCapabilities.RequiresPower2)
{
retval = true; // Return true to indicate the numbers changed

// Find the nearest base two log of the current width,
// and go up to the next integer
double exp = Math.Ceiling(Math.Log(width) / Math.Log(2));
// and use that as the exponent of the new width
width = (int)Math.Pow(2, exp);
// Repeat the process for height
exp = Math.Ceiling(Math.Log(height) / Math.Log(2));
height = (int)Math.Pow(2, exp);
}

if (Caps.TextureCapabilities.RequiresSquareOnly)
{
retval = true; // Return true to indicate numbers changed
width = Math.Max(width, height);
height = width;
}

newwidth = Math.Min(Caps.MaxTextureWidth, width);
newheight = Math.Min(Caps.MaxTextureHeight, height);
return retval;
}


private DepthStencilBuffer CreateDepthStencil(RenderTarget2D target)
{
return new DepthStencilBuffer(target.GraphicsDevice, target.Width,
target.Height, target.GraphicsDevice.DepthStencilBuffer.Format,
target.MultiSampleType, target.MultiSampleQuality);
}


The problem is that even with these methods my rendered model isn't showing up! :\

Here's my rendering code:

public override void Draw(SpriteBatch SBatch)
{
base.Draw(SBatch);

RenderTarget2D RenderTarget = CreateRenderTarget(m_Screen.ScreenMgr.GraphicsDevice, 1, SurfaceFormat.Depth32);

m_Screen.ScreenMgr.GraphicsDevice.SetRenderTarget(1, RenderTarget);
m_Screen.ScreenMgr.GraphicsDevice.DepthStencilBuffer = CreateDepthStencil(RenderTarget);

m_Effect.World = m_Screen.ScreenMgr.WorldMatrix;
m_Effect.View = m_Screen.ScreenMgr.ViewMatrix;
m_Effect.Projection = m_Screen.ScreenMgr.ProjectionMatrix;

if (m_HeadTexture != null)
{
m_Effect.Texture = m_HeadTexture;
m_Effect.TextureEnabled = true;

m_Effect.EnableDefaultLighting();
}

m_Effect.CommitChanges();

// Draw
m_Effect.Begin();
m_Effect.Techniques[0].Passes[0].Begin();

if (m_HeadVerticies != null)
{
foreach (Face Fce in m_CurrentHeadMesh.Faces)
{
VertexPositionNormalTexture[] Vertex = new VertexPositionNormalTexture[3];
Vertex[0] = m_HeadVerticies[Fce.AVertexIndex];
Vertex[1] = m_HeadVerticies[Fce.BVertexIndex];
Vertex[2] = m_HeadVerticies[Fce.CVertexIndex];

Vertex[0].TextureCoordinate = m_HeadVerticies[Fce.AVertexIndex].TextureCoordinate;
Vertex[1].TextureCoordinate = m_HeadVerticies[Fce.BVertexIndex].TextureCoordinate;
Vertex[2].TextureCoordinate = m_HeadVerticies[Fce.CVertexIndex].TextureCoordinate;

m_Screen.ScreenMgr.GraphicsDevice.DrawUserPrimitives<VertexPositionNormalTexture>(
PrimitiveType.TriangleList, Vertex, 0, 1);
}
}

m_Effect.Techniques[0].Passes[0].End();
m_Effect.End();
}



Here's the code for rendering a screen:

public virtual void Draw(SpriteBatch SBatch)
{
foreach (Texture2D Background in m_Backgrounds)
{
if (Background != null)
{
//Usually a screen only has 1 background, it should be drawn at 0, 0...
SBatch.Draw(Background, new Rectangle(0, 0, Background.Width, Background.Height),
Color.White);
}
}

//Networked UI elements are usually dialogs, so for now it is
//relatively safe to assume they can be drawn behind other elements.
foreach (NetworkedUIElement Element in m_NetUIElements)
Element.Draw(SBatch);

foreach (UIElement Element in m_UIElements)
{
if (Element.DrawingLevel == DrawLevel.DontGiveAFuck)
Element.Draw(SBatch);
}

foreach (UIElement Element in m_UIElements)
{
if (Element.DrawingLevel == DrawLevel.AlwaysOnTop)
Element.Draw(SBatch);
}

for (int i = 0; i < m_Popups.Count; i++)
m_Popups.Draw(SBatch);
}


What do I do to make my model show up?

I know that the rendering code works, because I've tested it in a tool I made.
So I'm thinking the reason the rendering isn't showing is because the model is rendered behind the textures...
Advertisement
Hi,

I'm not XNA specialist but there are some details in the code that stick in my eye.

- Seems that you create render target and depth/stencil target every time you call draw, without ever deleting them. You'll run out memory pretty quickly if you allocate screen sized render targets. Usually you'll create required number of render targets in the beginning of the program.

- You call SetRenderTarget with parameter (1, ...) but AFAIK the starting index should be 0. Unless of course if your pixel shader writes to the second render target.

- You should restore the previous render target / depthstencil after finishing drawing to the render target

- The render target surface format is [color="#660066"][font="CourierNew, monospace"]Depth32[/font]? It contains only 1 channel of data.

Cheers!
Thanks!

Calling SetRenderTarget() at the beginning of the game with a starting index of 0 and SurfaceFormat.Single seems to have made the rendertarget work.
The problem is that it insists on using the entire screen, even when I did:



int width, height;

// See if we can use our buffer size as our texture
/*CheckTextureSize(device.PresentationParameters.BackBufferWidth,
device.PresentationParameters.BackBufferHeight,
out width, out height);*/

// Create our render target
return new RenderTarget2D(device,
300, 500, numberLevels, surface,
type, 0);



Thus, I end up with a purple screen! :\
Maybe I have to restore the previous rendertarget? How do I do that?

Hi,

I'm not totally sure what kind of effect you are trying to achieve, but the generalized way of using render target(s) is something like:



[font="Arial"]- get current render target with GraphicsDevice.GetRenderTarget. This is your back buffer where you draw the final image. You should also store the current Depth/Stencil buffer.[/font]
[font="Arial"]- set new render target with [/font][font="Arial"]GraphicsDevice.SetRenderTarget and set new depth/stencil buffer[/font]
[font="Arial"]- draw required things to render target[/font]
[font="Arial"]- restore previous render target (back buffer) and depth/stencil buffer (use [/font][font="Arial"]GraphicsDevice.SetRenderTarget)[/font]
[font="Arial"]- draw screen using the render target as a texture in any way you desire. (for example full screen quad with the render target as a texture)[/font]
[font="Arial"]- Flip screen / Blit / Swap to show the image[/font]

[font="Arial"]Your surface format still looks funny to me. Check here for the description of the formats. For example,[/font][color="#660066"][font="CourierNew, monospace"]SurfaceFormat[/font][color="#666600"][font="CourierNew, monospace"].[/font]Color has 8-bit RGBA channels, which is typical for color render target.
I'm trying to achieve an effect like this:

jl1o2h.jpg

The part marked in red is a 3D model, everything else is rendered using 'normal' XNA rendering with 2D textures.

If I understand you correctly, I have to render the model as well as the textures to a rendertarget, and then... draw the screen using the rendertarget as a texture?
How do I use the rendertarget as a texture? Can I go (Texture2D)MyRenderTarget ?

I chose SurfaceFormat.Single because it seemed the reasonable thing to do, since there's only 1 channel.

Im trying to achieve an effect like this:

The part marked in red is a 3D model, everything else is rendered using 'normal' XNA rendering with 2D textures.

If I understand you correctly, I have to render the model as well as the textures to a rendertarget, and then... draw the screen using the rendertarget as a texture?
How do I use the rendertarget as a texture? Can I go (Texture2D)MyRenderTarget ?

I chose SurfaceFormat.Single because it seemed the reasonable thing to do, since there's only 1 channel.



Ok, that is a reasonable way to use render targets.

- You only need to render the 3d model to the render target. The "normal" part of drawing 2d textures is done after you restore your default back buffer and depth stencil buffer.
- 1 channel can store only one of the rgb components. You'll need at least 3 channels for storing all the required color information.
- To use render target as a texture, there should be a member function MyRenderTarget .[font="Consolas, Courier, monospace"]GetTexture() to get the texture.[/font]
[font="Consolas, Courier, monospace"] [/font]
[font="Consolas, Courier, monospace"]Cheers![/font]
Thanks for your help!

My rendering currently looks like:

a9tffo.png

Any idea why it looks like that? I have a feeling I need to clear the rendertarget, or something. Here's the current rendering-code;






public override void Draw(SpriteBatch SBatch)
{
base.Draw(SBatch);

if (m_HeadVerticies != null && m_HeadTexture != null)
{

RenderTarget BackBuffer = m_Screen.ScreenMgr.GraphicsDevice.GetRenderTarget(0);
DepthStencilBuffer DSBuffer = m_Screen.ScreenMgr.GraphicsDevice.DepthStencilBuffer;

m_Screen.ScreenMgr.GraphicsDevice.SetRenderTarget(0, m_Screen.ScreenMgr.RenderTarget);
m_Screen.ScreenMgr.GraphicsDevice.DepthStencilBuffer = CreateDepthStencil(m_Screen.ScreenMgr.RenderTarget);

m_Effect.World = m_Screen.ScreenMgr.WorldMatrix;
m_Effect.View = m_Screen.ScreenMgr.ViewMatrix;
m_Effect.Projection = m_Screen.ScreenMgr.ProjectionMatrix;

m_Effect.Texture = m_HeadTexture;
m_Effect.TextureEnabled = true;

m_Effect.EnableDefaultLighting();

m_Effect.CommitChanges();

// Draw
m_Effect.Begin();
m_Effect.Techniques[0].Passes[0].Begin();

foreach (Face Fce in m_CurrentHeadMesh.Faces)
{
VertexPositionNormalTexture[] Vertex = new VertexPositionNormalTexture[3];
Vertex[0] = m_HeadVerticies[Fce.AVertexIndex];
Vertex[1] = m_HeadVerticies[Fce.BVertexIndex];
Vertex[2] = m_HeadVerticies[Fce.CVertexIndex];

Vertex[0].TextureCoordinate = m_HeadVerticies[Fce.AVertexIndex].TextureCoordinate;
Vertex[1].TextureCoordinate = m_HeadVerticies[Fce.BVertexIndex].TextureCoordinate;
Vertex[2].TextureCoordinate = m_HeadVerticies[Fce.CVertexIndex].TextureCoordinate;

m_Screen.ScreenMgr.GraphicsDevice.DrawUserPrimitives<VertexPositionNormalTexture>(
PrimitiveType.TriangleList, Vertex, 0, 1);
}

m_Effect.Techniques[0].Passes[0].End();
m_Effect.End();

m_Screen.ScreenMgr.GraphicsDevice.SetRenderTarget(0, (RenderTarget2D)BackBuffer);
m_Screen.ScreenMgr.GraphicsDevice.DepthStencilBuffer = DSBuffer;

SBatch.Draw(m_Screen.ScreenMgr.RenderTarget.GetTexture(), new Rectangle(m_X, m_Y, m_Width, m_Height),
Color.White);
}
}


Thanks for your help!
My rendering currently looks like:
Any idea why it looks like that? I have a feeling I need to clear the rendertarget, or something. Here's the current rendering-code;



Yes, render target is just like any buffer where you draw. It requires clearing before using it. Also you'll need to clear your depth/stencil buffer.

I can see in the code too that you are creating depth/stencil target every time you draw. As far as I know creating such resources every frame should be avoided.

As for any correct rendering you'll need to set your viewport parameters too to reflect the current render target, especially if the render target has different size compared to the default back buffer.

Cheers!
[font="Verdana, Arial, Helvetica, sans-serif"]Thanks for all your help so far![/font][font="Verdana, Arial, Helvetica, sans-serif"]
[font="Verdana, Arial, Helvetica, sans-serif"]My render currently looks like;[/font]
[font="Verdana, Arial, Helvetica, sans-serif"] [/font]2e3tnxh.png[/font]
[font="Verdana, Arial, Helvetica, sans-serif"] [/font]
[font="Verdana, Arial, Helvetica, sans-serif"]I'm currently experimenting with using different SurfaceFormat options for my RenderTarget2D instance.[/font]
[font="Verdana, Arial, Helvetica, sans-serif"]Currently I'm using SurfaceFormat.Rgba32, which results in an output like in the above picture. When using SurfaceFormat.Color, I get a transparent background, but the model is more or less black.[/font]
[font="Verdana, Arial, Helvetica, sans-serif"]I'm currently using DepthFormat.Depth32 for the DepthStencilBuffer, but what I'm using for this seems to have no effect on the output.[/font]
[font="Verdana, Arial, Helvetica, sans-serif"]Any ideas on how to get lighting to work?[/font]
[font="Verdana, Arial, Helvetica, sans-serif"] [/font]
[font="Verdana, Arial, Helvetica, sans-serif"] [/font][font="Verdana, Arial, Helvetica, sans-serif"]/// <summary>
/// Loads a head mesh.
/// </summary>
/// <param name="MeshID">The ID of the mesh to load.</param>
/// <param name="TexID">The ID of the texture to load.</param>
public void LoadHeadMesh(Appearance Apr)
{
//Appearance Apr = new Appearance(ContentManager.GetResourceFromLongID(AppearanceID));
Binding Bnd = new Binding(ContentManager.GetResourceFromLongID(Apr.BindingIDs[0]));

m_CurrentHeadMesh = new Mesh(ContentManager.GetResourceFromLongID(Bnd.MeshAssetID));
m_HeadVerticies = m_CurrentHeadMesh.ProcessMesh();

m_HeadTexture = Texture2D.FromFile(m_Screen.ScreenMgr.GraphicsDevice,
new MemoryStream(ContentManager.GetResourceFromLongID(Bnd.TextureAssetID)));

m_RenderTarget = CreateRenderTarget(m_Screen.ScreenMgr.GraphicsDevice, 0, SurfaceFormat.Rgba32);
m_DSBuffer = CreateDepthStencil(m_RenderTarget, DepthFormat.Depth32);
}

public override void Update(GameTime GTime)
{
m_Rotation += 0.05f;
m_Screen.ScreenMgr.WorldMatrix = Matrix.CreateRotationX(m_Rotation);

base.Update(GTime);
}

public override void Draw(SpriteBatch SBatch)
{
base.Draw(SBatch);

if (m_HeadVerticies != null && m_HeadTexture != null)
{
RenderTarget BackBuffer = m_Screen.ScreenMgr.GraphicsDevice.GetRenderTarget(0);
DepthStencilBuffer DSBuffer = m_Screen.ScreenMgr.GraphicsDevice.DepthStencilBuffer;

m_Screen.ScreenMgr.GraphicsDevice.SetRenderTarget(0, m_RenderTarget);
m_Screen.ScreenMgr.GraphicsDevice.DepthStencilBuffer = CreateDepthStencil(m_RenderTarget);

m_Screen.ScreenMgr.GraphicsDevice.Clear(ClearOptions.Target, Color.TransparentBlack, 0, 0);
m_Screen.ScreenMgr.GraphicsDevice.Clear(ClearOptions.DepthBuffer, Color.Tomato, 0, 0);

Viewport VPort = new Viewport();
VPort.Width = m_Width;
VPort.Height = m_Height;
m_Screen.ScreenMgr.GraphicsDevice.Viewport = VPort;

m_Effect.World = m_Screen.ScreenMgr.WorldMatrix;
m_Effect.View = m_Screen.ScreenMgr.ViewMatrix;
m_Effect.Projection = m_Screen.ScreenMgr.ProjectionMatrix;

m_Effect.Texture = m_HeadTexture;
m_Effect.TextureEnabled = true;

m_Effect.EnableDefaultLighting();

m_Effect.CommitChanges();

// Draw
m_Effect.Begin();
m_Effect.Techniques[0].Passes[0].Begin();

foreach (Face Fce in m_CurrentHeadMesh.Faces)
{
VertexPositionNormalTexture[] Vertex = new VertexPositionNormalTexture[3];
Vertex[0] = m_HeadVerticies[Fce.AVertexIndex];
Vertex[1] = m_HeadVerticies[Fce.BVertexIndex];
Vertex[2] = m_HeadVerticies[Fce.CVertexIndex];

Vertex[0].TextureCoordinate = m_HeadVerticies[Fce.AVertexIndex].TextureCoordinate;
Vertex[1].TextureCoordinate = m_HeadVerticies[Fce.BVertexIndex].TextureCoordinate;
Vertex[2].TextureCoordinate = m_HeadVerticies[Fce.CVertexIndex].TextureCoordinate;

m_Screen.ScreenMgr.GraphicsDevice.DrawUserPrimitives<VertexPositionNormalTexture>(
PrimitiveType.TriangleList, Vertex, 0, 1);
}

m_Effect.Techniques[0].Passes[0].End();
m_Effect.End();

m_Screen.ScreenMgr.GraphicsDevice.SetRenderTarget(0, (RenderTarget2D)BackBuffer);
m_Screen.ScreenMgr.GraphicsDevice.DepthStencilBuffer = DSBuffer;

SBatch.Draw(m_RenderTarget.GetTexture(), new Rectangle(m_X, m_Y, m_Width, m_Height),
Color.White);
}
}[/font][font="Verdana, Arial, Helvetica, sans-serif"]

[/font]
[font="Verdana, Arial, Helvetica, sans-serif"] [/font]
Are you able to render the 3D model by itself without a render target ?

This topic is closed to new replies.

Advertisement