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 加载的纹理中。不幸的是,以这种方式获得的纹理会在游戏窗口最小化后消失。我读了一些关于这个问题的东西,很多事情表明 RenderTarget2D 是错误的,因为渲染目标毕竟设置为 null。我应该怎么做才能永久保留我的纹理?

编辑 - 固定代码

好的,我使用了第四个选项,它工作得很好。这是固定代码:

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 Texture2D. 在as Texture2D你的方法结束时,它什么也不做。您制作的演员表可能是隐含的。强制转换不对引用的对象实例进行任何更改。您可以将其转换回 aRenderTarget2D并且再次,它不会更改对象本身。

RenderTarget2D继承自的原因Texture2D是您可以将渲染目标传递给任何需要纹理并使其正常工作的方法。但是它们的底层功能有一些重要的区别:

Direct3D/GPU 层,发生的事情是您收到“设备丢失”错误,因为您使用的设备上下文消失了(由于窗口被最小化 - 但这不是唯一的事情这可能会导致它)。这意味着您将失去所有您正在使用的 GPU 内存 - 包括纹理和渲染目标。

常规(您使用orTexture2D加载或设置)维护数据的 CPU 端副本。因此,当设备丢失时,XNA 会自动从 CPU 端副本重新创建该纹理的内容。ContentManager.LoadTexture2D.FromStreamSetData

但是 aRenderTarget2D完全保存在 GPU 上。如果它丢失,XNA 无法重新创建它。每当您更改它时,获取其内容的 CPU 端副本将需要从 GPU 获取极其昂贵的副本。


以下是您修复它的方法:

  • 选项 1始终在每帧开始时重新渲染渲染目标的内容。这是使用渲染目标的标准方式,因为通常每帧都会更改它们的内容。不适用于您的情况。

  • 选项 2RenderTarget2D.ContentLost是通过重新创建渲染目标的内容来响应事件。(或者:检查IsContentLost每一帧的标志。)

  • 选项 3是创建纹理的 CPU 端副本。基本上从渲染目标获取数据GetData。然后创建一个新Texture2D的并将数据设置到它上面SetData。XNA 将为您处理任何设备丢失(如上所述)。

  • 选项 4是根本不使用渲染目标!用于GetData获取纹理数据,在软件中执行转换,然后使用SetData. 看到纹理数据无论如何都会被复制 - 为什么不自己复制并同时预乘呢?

  • 选项 5FromStream用加载时预乘的东西替换。这类似于选项 4,但为您节省了一些副本。大概是矫枉过正。

  • 选项 6是首先以预乘格式存储您的纹理。尽管此时您也可能正在使用内容管道。

就您个人而言,我可能会根据您的情况选择选项 4


最后:不要忘记调用Dispose您自己创建的任何资源(纹理、渲染目标等)(使用newor FromStream,但不是from 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 回答