9

ここにいる XNA 初心者は、毎日学習しています。RenderTarget2D を使用して、複数のテクスチャを 1 つに合成する方法を考え出しました。ただし、ほとんどの目的で RenderTarget2D を Texture2D として使用できますが、決定的な違いがあります。これらのレンダリングされたテクスチャは、バックバッファーのサイズが変更されると失われます (グラフィック デバイスのメモリが不足しているなどの他の状況では間違いありません)。

今のところ、完成した RenderTarget2D を新しい不揮発性 Texture2D オブジェクトにコピーしているだけです。ただし、そうするための私のコードはかなり醜いです。これを行うより優雅な方法はありますか?疲れているだけかもしれませんが、Google や SO で答えが見つかりません。

少し単純化:

public static Texture2D  MergeTextures(int width, int height, IEnumerable<Tuple<Texture2D, Color>> textures)
    {
    RenderTarget2D  buffer = new RenderTarget2D(_device, width, height);

    _device.SetRenderTarget(buffer);
    _device.Clear(Color.Transparent);

    SpriteBatch  spriteBatch = new SpriteBatch(_device);
    spriteBatch.Begin(SpriteSortMode.Immediate, BlendState.NonPremultiplied);

    // Paint each texture over the one before, in the appropriate color
    Rectangle  rectangle = new Rectangle(0, 0, width, height);
    foreach (Tuple<Texture2D, Color> texture in textures)
        spriteBatch.Draw(texture.Item1, rectangle, texture.Item2);

    spriteBatch.End();
    _device.SetRenderTarget((RenderTarget2D)null);

    // Write the merged texture to a Texture2D, so we don't lose it when resizing the back buffer
    // This is POWERFUL ugly code, and probably terribly, terribly slow
    Texture2D  mergedTexture = new Texture2D(_device, width, height);
    Color[]    content       = new Color[width * height];
    buffer.GetData<Color>(content);
    mergedTexture.SetData<Color>(content);
    return mergedTexture;
    }

IsContentLostをチェックして必要に応じて再レンダリングする必要があると思いますが、これはメインの描画ループの途中で発生し、もちろん SpriteBatches をネストすることはできません。「レンダリング TODO」リストを維持し、メインの SpriteBatch が終了した後にそれらを処理すると、次のフレームで使用できるようになります。それは好ましい戦略ですか?

このコードは数回しか呼び出されないので、パフォーマンスは問題になりませんが、正しく行う方法を学びたいと思います。

4

3 に答える 3

4

実際、コンテンツを通常ロードするとき (ゲームの開始、レベルの変更、部屋の変更など) に 1 回限りのプロセスでテクスチャを生成している場合、コードはそれほど悪くはありません。CPU と GPU の間でテクスチャを転送しています。これは、普通のテクスチャをロードするのと同じことです。それは簡単で、うまくいきます!

テクスチャをより頻繁に生成していて、ロード時間のコストではなく、フレームごとのコストになり始めている場合は、そのパフォーマンスと、おそらくそれらをレンダー ターゲットとして維持することを心配する必要があります。

描画の途中ではいけないのでContentLost、安全にそのイベントに応答して、レンダー ターゲットを再作成することができます。IsContentLostまたは、他のものをレンダリングする前に、理想的にはフレームの開始時にそれぞれをチェックできます。SpriteBatchいずれにせよ、開始前にすべてを確認する必要があります。

(通常、レンダー ターゲットを使用する場合はフレームごとに再生成するため、その場合はチェックする必要はありません。)

于 2011-04-01T05:50:03.650 に答える
2

交換

Texture2D  mergedTexture = new Texture2D(_device, width, height);
Color[]    content       = new Color[width * height];
buffer.GetData<Color>(content);
mergedTexture.SetData<Color>(content);
return mergedTexture;

return buffer;

RenderTarget2D は Texture2D を拡張するため、Texture2D クラス データが返されるだけです。また、興味がある場合は、複数のテクスチャから GUI ライブラリのウィジェットを構築するために作成したクラスを次に示します。この種のことをたくさん行う必要がある場合。

using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using System.IO;

namespace Voodo.Utils {

    /// <summary>
    /// 
    /// </summary>
    public class TextureBaker {

        private readonly SpriteBatch _batch;
        private readonly RenderTarget2D _renderTarget;
        private readonly GraphicsDevice _graphicsDevice;

        /// <summary>
        /// 
        /// </summary>
        public Rectangle Bounds {
            get { return _renderTarget.Bounds; }
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="graphicsDevice"></param>
        /// <param name="size"></param>
        public TextureBaker(GraphicsDevice graphicsDevice, Vector2 size) {

            _graphicsDevice = graphicsDevice;

            _batch = new SpriteBatch(_graphicsDevice);
            _renderTarget = new RenderTarget2D(
                _graphicsDevice, 
                (int)size.X, 
                (int)size.Y);

            _graphicsDevice.SetRenderTarget(_renderTarget);

            _graphicsDevice.Clear(Color.Transparent);

            _batch.Begin(
                SpriteSortMode.Immediate, 
                BlendState.AlphaBlend, 
                SamplerState.LinearClamp,
                DepthStencilState.Default, 
                RasterizerState.CullNone);
        }

        #region Texture2D baking

        /// <summary>
        /// 
        /// </summary>
        /// <param name="texture"></param>
        public void BakeTexture(Texture2D texture) {

            _batch.Draw(
                texture,
                new Rectangle(0, 0, Bounds.Width, Bounds.Height), 
                Color.White);
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="texture"></param>
        /// <param name="destination"></param>
        public void BakeTexture(Texture2D texture, Rectangle destination) {

            _batch.Draw(
                texture,
                destination,
                Color.White);
        }        

        /// <summary>
        /// 
        /// </summary>
        /// <param name="texture"></param>
        /// <param name="destination"></param>
        /// <param name="source"></param>
        public void BakeTexture(Texture2D texture, Rectangle destination, Rectangle source) {

            _batch.Draw(
                texture,
                destination,
                source,
                Color.White);
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="texture"></param>
        /// <param name="sourceModification"></param>
        /// <param name="destination"></param>
        public void BakeTexture(Texture2D texture, System.Drawing.RotateFlipType sourceModification, Rectangle destination) {

            Stream sourceBuffer = new MemoryStream();
            texture.SaveAsPng(sourceBuffer, texture.Width, texture.Height);

            System.Drawing.Image sourceImage = System.Drawing.Image.FromStream(sourceBuffer);

            sourceBuffer = new MemoryStream();
            sourceImage.RotateFlip(sourceModification);
            sourceImage.Save(sourceBuffer, System.Drawing.Imaging.ImageFormat.Png);                       

            _batch.Draw(
                Texture2D.FromStream(_graphicsDevice, sourceBuffer),
                destination,
                Color.White);
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="texture"></param>
        /// <param name="sourceModification"></param>
        /// <param name="destination"></param>
        /// <param name="source"></param>
        public void BakeTexture(Texture2D texture, System.Drawing.RotateFlipType sourceModification, Rectangle destination, Rectangle source) {

            Stream sourceBuffer = new MemoryStream();
            texture.SaveAsPng(sourceBuffer, texture.Width, texture.Height);

            System.Drawing.Image sourceImage = System.Drawing.Image.FromStream(sourceBuffer);

            sourceBuffer = new MemoryStream();
            sourceImage.RotateFlip(sourceModification);
            sourceImage.Save(sourceBuffer, System.Drawing.Imaging.ImageFormat.Png);

            _batch.Draw(
                Texture2D.FromStream(_graphicsDevice, sourceBuffer),
                destination,
                source,
                Color.White);
        }

        #endregion

        #region SpriteFont baking

        /// <summary>
        /// 
        /// </summary>
        /// <param name="font"></param>
        /// <param name="text"></param>
        /// <param name="location"></param>
        /// <param name="textColor"></param>
        public void BakeText(SpriteFont font, string text, Vector2 location, Color textColor) {

            _batch.DrawString(font, text, location, textColor);
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="font"></param>
        /// <param name="text"></param>
        /// <param name="location"></param>
        public void BakeTextCentered(SpriteFont font, string text, Vector2 location, Color textColor) {

            var shifted = new Vector2 {
                X = location.X - font.MeasureString(text).X / 2,
                Y = location.Y - font.MeasureString(text).Y / 2
            };

            _batch.DrawString(font, text, shifted, textColor);
        }

        #endregion

        /// <summary>
        /// 
        /// </summary>
        /// <returns></returns>
        public Texture2D GetTexture() {

            _batch.End();
            _graphicsDevice.SetRenderTarget(null);

            return _renderTarget;
        }
    }
}
于 2011-05-01T07:36:49.520 に答える
0

レンダー ターゲットを別の場所に描画するときに動的にサイズ変更される問題がある場合は、次のように完成した RT をコピーする設定サイズのオフスクリーン レンダー ターゲットを使用できます。

Rendertarget2D offscreenRT = new RenderTarget2D(_device, width, height);
_device.SetRenderTarget(offscreenRT);
_device.Clear(Color.Transparent);

SpriteBatch  spriteBatch = new SpriteBatch(_device);
spriteBatch.Begin(SpriteSortMode.Immediate, BlendState.NonPremultiplied);
spriteBatch.Draw(buffer, Vector2.Zero, Color.White);
spriteBatch.End();
_device.SetRenderTarget(null);
于 2011-04-01T11:02:53.467 に答える