Jump to content

  • Log In with Google      Sign In   
  • Create Account


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


Old topic!
Guest, the last post of this topic is over 60 days old and at this point you may not reply in this topic. If you wish to continue this conversation start a new topic.

  • You cannot reply to this topic
14 replies to this topic

#1 MatsK   Members   -  Reputation: 226

Like
0Likes
Like

Posted 17 November 2011 - 10:19 AM

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[i].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...

Sponsor:

#2 kauna   Crossbones+   -  Reputation: 2289

Like
1Likes
Like

Posted 17 November 2011 - 10:49 AM

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 Depth32? It contains only 1 channel of data.

Cheers!

#3 MatsK   Members   -  Reputation: 226

Like
0Likes
Like

Posted 17 November 2011 - 03:46 PM

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?

#4 kauna   Crossbones+   -  Reputation: 2289

Like
1Likes
Like

Posted 18 November 2011 - 01:50 AM

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:


- 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.
- set new render target with GraphicsDevice.SetRenderTarget and set new depth/stencil buffer
- draw required things to render target
- restore previous render target (back buffer) and depth/stencil buffer (use GraphicsDevice.SetRenderTarget)
- 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)
- Flip screen / Blit / Swap to show the image

Your surface format still looks funny to me. Check here for the description of the formats. For example,SurfaceFormat.Color has 8-bit RGBA channels, which is typical for color render target.

#5 MatsK   Members   -  Reputation: 226

Like
1Likes
Like

Posted 18 November 2011 - 06:17 AM

I'm trying to achieve an effect like this:

Posted Image

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.

#6 kauna   Crossbones+   -  Reputation: 2289

Like
1Likes
Like

Posted 18 November 2011 - 08:40 AM

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 .GetTexture() to get the texture.

Cheers!

#7 MatsK   Members   -  Reputation: 226

Like
0Likes
Like

Posted 19 November 2011 - 09:20 AM

Thanks for your help!

My rendering currently looks like:

Posted Image

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);
            }
        }


#8 kauna   Crossbones+   -  Reputation: 2289

Like
1Likes
Like

Posted 19 November 2011 - 02:39 PM

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!

#9 MatsK   Members   -  Reputation: 226

Like
0Likes
Like

Posted 20 November 2011 - 01:33 PM

Thanks for all your help so far!
My render currently looks like;
Posted Image


I'm currently experimenting with using different SurfaceFormat options for my RenderTarget2D instance.
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.
I'm currently using DepthFormat.Depth32 for the DepthStencilBuffer, but what I'm using for this seems to have no effect on the output.
Any ideas on how to get lighting to work?

/// <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);
            }
        }[/size][/font][font="Verdana, Arial, Helvetica, sans-serif"][size="2"]



#10 Endemoniada   Members   -  Reputation: 308

Like
0Likes
Like

Posted 20 November 2011 - 07:26 PM

Are you able to render the 3D model by itself without a render target ?

#11 MatsK   Members   -  Reputation: 226

Like
0Likes
Like

Posted 20 November 2011 - 11:30 PM

Yup, and lighting seems to work fine when not using a rendetarget.

#12 phil_t   Crossbones+   -  Reputation: 3235

Like
0Likes
Like

Posted 21 November 2011 - 01:22 AM

Take a step back for a minute. Do you really need to use a render target at all?

Just draw all your 2d GUI, then draw the models on top. That should work unless you're actually using the depth buffer when drawing your GUI. But assuming you're drawing it with SpriteBatch, depth-writing is disabled by default.

Once you've done drawing the GUI, then set the appropriate states for rendering 3d models (enable the depth buffer, enable depth writing, turn off alpha blending) and draw your model.

(btw, it looks like you're using XNA 3.0. I would consider upgrading to XNA 4.0. The API has had a number of improvements that make it more difficult to do the wrong thing :-)).

#13 kauna   Crossbones+   -  Reputation: 2289

Like
0Likes
Like

Posted 21 November 2011 - 07:28 AM

Take a step back for a minute. Do you really need to use a render target at all?
Just draw all your 2d GUI, then draw the models on top. That should work unless you're actually using the depth buffer when drawing your GUI. But assuming you're drawing it with SpriteBatch, depth-writing is disabled by default.
Once you've done drawing the GUI, then set the appropriate states for rendering 3d models (enable the depth buffer, enable depth writing, turn off alpha blending) and draw your model.
(btw, it looks like you're using XNA 3.0. I would consider upgrading to XNA 4.0. The API has had a number of improvements that make it more difficult to do the wrong thing :-)).


Otherwise, there is still some improvement for the code with render target :

- you can clear your render target and depth buffer with one call. No need to call clear twice (this isn't an error though).
- Projection matrix : you have to calculate correct projection matrix to use with the render target. You may not use the same projection matrix as with the full screen since the aspect ratio is different.
- SurfaceFormat.Color should be the correct color buffer format

However, I can't think why your model shows up black.

Cheers!

#14 MatsK   Members   -  Reputation: 226

Like
0Likes
Like

Posted 21 November 2011 - 03:11 PM

SurfaceFormat.Color should be the correct color buffer format



What?

... I'm thinking that the current aspectratio might be banning me from using non-power-of-two-textures... but I'm not sure.

#15 kauna   Crossbones+   -  Reputation: 2289

Like
0Likes
Like

Posted 21 November 2011 - 04:08 PM

SurfaceFormat.Color should be the correct color buffer format



What?

... I'm thinking that the current aspectratio might be banning me from using non-power-of-two-textures... but I'm not sure.


Well I guess it's my turn to say "what"?
By saying "Color buffer format" I mean render target format. As you are storing color in it it is correct to say "color buffer". You'll need RGB channels for your render target and 8-bits per channel is enough. The closest thing to that is a surface format RGBA with 8-bits per channel. SurfaceFormat.Color stands for such surface format.


Nothing is banning you from using non-power-of-two-textures except the hardware if it doesn't support them (which is rare these days).
Just calculate a correct projection matrix to reflect the size / aspect of the render target.


Cheers!









Old topic!
Guest, the last post of this topic is over 60 days old and at this point you may not reply in this topic. If you wish to continue this conversation start a new topic.



PARTNERS