1

ディレクトリからテクスチャをロードする際に問題が発生しました。たぶん最初にコードを書く:

private Texture2D LoadTextureStream(string filePath)
{
    Texture2D file = null;
    RenderTarget2D result = null;

    try
    {
        using (System.IO.Stream titleStream = TitleContainer.OpenStream(filePath))
        {
            file = Texture2D.FromStream(GraphicsDevice, titleStream);
        }
    }
    catch
    {
        throw new System.IO.FileLoadException("Cannot load '" + filePath + "' file!");
    }
    PresentationParameters pp = GraphicsDevice.PresentationParameters;
    //Setup a render target to hold our final texture which will have premulitplied alpha values
    result = new RenderTarget2D(GraphicsDevice, file.Width, file.Height, true, pp.BackBufferFormat, pp.DepthStencilFormat);

    GraphicsDevice.SetRenderTarget(result);
    GraphicsDevice.Clear(Color.Black);

    //Multiply each color by the source alpha, and write in just the color values into the final texture
    BlendState blendColor = new BlendState();
    blendColor.ColorWriteChannels = ColorWriteChannels.Red | ColorWriteChannels.Green | ColorWriteChannels.Blue;

    blendColor.AlphaDestinationBlend = Blend.Zero;
    blendColor.ColorDestinationBlend = Blend.Zero;

    blendColor.AlphaSourceBlend = Blend.SourceAlpha;
    blendColor.ColorSourceBlend = Blend.SourceAlpha;

    SpriteBatch spriteBatch = new SpriteBatch(GraphicsDevice);
    spriteBatch.Begin(SpriteSortMode.Immediate, blendColor);
    spriteBatch.Draw(file, file.Bounds, Color.White);
    spriteBatch.End();

    //Now copy over the alpha values from the PNG source texture to the final one, without multiplying them
    BlendState blendAlpha = new BlendState();
    blendAlpha.ColorWriteChannels = ColorWriteChannels.Alpha;

    blendAlpha.AlphaDestinationBlend = Blend.Zero;
    blendAlpha.ColorDestinationBlend = Blend.Zero;

    blendAlpha.AlphaSourceBlend = Blend.One;
    blendAlpha.ColorSourceBlend = Blend.One;

    spriteBatch.Begin(SpriteSortMode.Immediate, blendAlpha);
    spriteBatch.Draw(file, file.Bounds, Color.White);
    spriteBatch.End();

    //Release the GPU back to drawing to the screen
    GraphicsDevice.SetRenderTarget(null);

    return result as Texture2D;
}

まず、テクスチャがストリームから読み込まれます。次に、いくつかのブレンドオプションを変更して、ContentPipelineによってロードされたテクスチャなどの動作に到達します。残念ながら、この方法で取得されたテクスチャは、ゲームウィンドウを最小化すると消えます。私はこの問題について何かを読みましたが、レンダリングターゲットが結局nullに設定されているため、RenderTarget2Dに障害があることを多くのことが示しています。テクスチャを永続的に保持するにはどうすればよいですか?

編集-修正されたコード

OK、4番目のオプションを使用しましたが、完全に機能します。修正されたコードは次のとおりです。

private Texture2D LoadTextureStream(string filePath)
{
    Texture2D file = null;
    Texture2D resultTexture;
    RenderTarget2D result = null;

    try
    {
        using (System.IO.Stream titleStream = TitleContainer.OpenStream(filePath))
        {
            file = Texture2D.FromStream(GraphicsDevice, titleStream);
        }
    }
    catch
    {
        throw new System.IO.FileLoadException("Cannot load '" + filePath + "' file!");
    }
    PresentationParameters pp = GraphicsDevice.PresentationParameters;
    //Setup a render target to hold our final texture which will have premulitplied alpha values
    result = new RenderTarget2D(GraphicsDevice, file.Width, file.Height, true, pp.BackBufferFormat, pp.DepthStencilFormat);

    GraphicsDevice.SetRenderTarget(result);
    GraphicsDevice.Clear(Color.Black);

    //Multiply each color by the source alpha, and write in just the color values into the final texture
    BlendState blendColor = new BlendState();
    blendColor.ColorWriteChannels = ColorWriteChannels.Red | ColorWriteChannels.Green | ColorWriteChannels.Blue;

    blendColor.AlphaDestinationBlend = Blend.Zero;
    blendColor.ColorDestinationBlend = Blend.Zero;

    blendColor.AlphaSourceBlend = Blend.SourceAlpha;
    blendColor.ColorSourceBlend = Blend.SourceAlpha;

    SpriteBatch spriteBatch = new SpriteBatch(GraphicsDevice);
    spriteBatch.Begin(SpriteSortMode.Immediate, blendColor);
    spriteBatch.Draw(file, file.Bounds, Color.White);
    spriteBatch.End();

    //Now copy over the alpha values from the PNG source texture to the final one, without multiplying them
    BlendState blendAlpha = new BlendState();
    blendAlpha.ColorWriteChannels = ColorWriteChannels.Alpha;

    blendAlpha.AlphaDestinationBlend = Blend.Zero;
    blendAlpha.ColorDestinationBlend = Blend.Zero;

    blendAlpha.AlphaSourceBlend = Blend.One;
    blendAlpha.ColorSourceBlend = Blend.One;

    spriteBatch.Begin(SpriteSortMode.Immediate, blendAlpha);
    spriteBatch.Draw(file, file.Bounds, Color.White);
    spriteBatch.End();

    //Release the GPU back to drawing to the screen
    GraphicsDevice.SetRenderTarget(null);

    resultTexture = new Texture2D(GraphicsDevice, result.Width, result.Height);
    Color[] data = new Color[result.Height * result.Width];
    Color[] textureColor = new Color[result.Height * result.Width];

    result.GetData<Color>(textureColor);

    for (int i = 0; i < result.Height; i++)
    {
        for (int j = 0; j < result.Width; j++)
        {
            data[j + i * result.Width] = textureColor[j + i * result.Width];
        }
    }

    resultTexture.SetData(data);

    return resultTexture;
}

助けてくれてありがとう!

4

1 に答える 1

6

これは私を狂わせ始めています。私は何度もこの問題に答えてきました。だから私はこれを決定的なものにしようとします。

この質問が何度も返ってきます。まず第一に、XNA の文書化が不十分なためです。また、人々がこのようなコードをチュートリアルやフォーラムに投稿し続け、機能しているように見えるので「大丈夫」だと主張し続けているためです...ウィンドウとすべてを最小化するまであなたのテクスチャが行方不明になります!さらに、GPU で作業を行うことにより、より高速でなければならないという誤解があります (そうではない可能性があります)。


何が起こっているかは次のとおりです。

C#/CPU レイヤーでは、 aRenderTarget2D aTexture2Dです。メソッドのas Texture2D最後には、まったく何もしません。作成するキャストは暗黙的である可能性があります。キャストは、参照されたオブジェクト インスタンスに変更を加えません。キャストして a に戻すこともできますがRenderTarget2D、オブジェクト自体は変更されません。

RenderTarget2Dから継承する理由Texture2Dは、テクスチャを期待する任意のメソッドにレンダー ターゲットを渡して、正しく動作させることができるようにするためです。ただし、それらの基本的な機能にはいくつかの重要な違いがあります。

Direct3D/GPU レイヤーで、使用していたデバイス コンテキストがなくなったため (ウィンドウが最小化されたため)、「デバイスが失われました」というエラーが発生していますが、それだけではありません。引き起こす可能性があります)。これは、使用していたすべての GPU メモリ (テクスチャとレンダー ターゲットを含む) が失われることを意味します。

通常のTexture2D( または でロードするContentManager.LoadTexture2D.FromStream、 で設定するSetData) は、データの CPU 側のコピーを維持します。そのため、デバイスが失われると、XNA は CPU 側のコピーからそのテクスチャのコンテンツを自動的に再作成します。

ただし、 aRenderTarget2Dは完全に GPU 上に保持されます。XNA は、行方不明になった場合に再作成する方法がありません。そのコンテンツの CPU 側のコピーを取得するには、変更するたびに GPU から非常にコストのかかるコピーを返す必要があります。


修正方法は次のとおりです。

  • オプション 1は、各フレームの開始時にレンダー ターゲットのコンテンツを常に再レンダリングすることです。これは、レンダー ターゲットを使用する標準的な方法です。通常はフレームごとにコンテンツを変更するためです。あなたのケースには実際には当てはまりません。

  • オプション 2RenderTarget2D.ContentLostは、レンダー ターゲットのコンテンツを再作成することによってイベントに応答することです。IsContentLost(または、フレームごとにフラグをチェックします。)

  • オプション 3は、テクスチャの CPU 側のコピーを作成することです。基本的にはレンダーターゲットからデータを取得しますGetData。次に、新しいTexture2Dを作成し、 でデータを設定しますSetData。その後、XNA がデバイスの損失を処理します (前述のとおり)。

  • オプション 4は、レンダー ターゲットをまったく使用しないことです。を使用GetDataしてテクスチャ データを取得し、ソフトウェアで変換を実行してから、 で元に戻しSetDataます。とにかくテクスチャ データがコピーされているのを見て、自分でコピーを行い、同時に事前乗算してみませんか?

  • オプション 5FromStreamは、ロード時に事前乗算するものに置き換えることです。これはオプション 4 に似ていますが、いくつかのコピーを節約できます。おそらくやり過ぎです。

  • オプション 6は、最初にテクスチャを乗算済みの形式で保存することです。ただし、この時点では、コンテンツ パイプラインを使用することもできます。

個人的には、おそらくあなたの状況ではオプション 4を選択します。


Dispose最後に: 使い終わったリソース (テクスチャ、レンダー ターゲットなど)を呼び出すことを忘れnewないFromStreamくださいContentManager

あなたのコードでは、あなたが漏れていることに気付きましたTexture2D file


いくつかの狂気を救うために、CPU 上でテクスチャを完全に事前乗算する、単純でテストされていない方法を回答に追加します。

public static void PremultiplyTexture(Texture2D texture)
{
    Color[] buffer = new Color[texture.Width * texture.Height];
    texture.GetData(buffer);
    for(int i = 0; i < buffer.Length; i++)
    {
        buffer[i] = Color.FromNonPremultiplied(
                buffer[i].R, buffer[i].G, buffer[i].B, buffer[i].A);
    }
    texture.SetData(buffer);
}
于 2012-11-25T14:11:03.410 に答える