SlimDX / DirectX9 / C# - 如何访问纹理中的像素数据

时间:2011-08-11 22:39:07

标签: directx directx-9 slimdx

这是我在StackOverflow上的第一个问题,欢呼!老实说,我每天都会使用StackOverflow来处理我的工作和个人编程之谜。 99.9%的时间我实际上也找到了我需要的答案,这太棒了!

我当前的问题实际上让我感到困惑,因为我似乎找不到任何实际可行的东西。我已经阅读了GameDev.net上的几篇帖子,并在网络上找到了其他资源但无法对其进行排序。

我正在将我为XNA编写的小型2D引擎移植到SlimDX(目前只是DirectX9),这是一个很好的举措,因为我在短短几天内学到了更多关于DirectX内部工作的知识。我在六个月内与XNA合作。我完成了大部分基本渲染功能,并且实际上设法重新创建了XNA SpriteBatch,其中包含许多其他功能(我在XNA中真的很想念)。

我试图开始工作的最后一件事是从给定纹理中提取源矩形并将其用于平铺。原因:当没有平铺时,您可以随意使用UV来获取您想要显示的源(例如:0.3; 0.3到0.5; 0.5),但是当平铺时需要UV来平铺(0; 0到2; 2)表示平铺图像两次),因此需要剪切纹理。

总而言之,我尝试使用以下内容:

DataRectangle dataRectangle = sprite.Texture.LockRectangle(0, LockFlags.None);
Format format = sprite.Texture.GetLevelDescription(0).Format;

byte[] buffer = new byte[4];
dataRectangle.Data.Read(buffer, ([y] * dataRectangle.Pitch) + ([x] * 4), buffer.Length);
texture.UnlockRectangle(0);

我尝试了不同的像素,但似乎都给出了伪造的数据。例如,我实际上尝试使用我当前的头像来查看我从DataRectangle获得的缓冲区是否与图像中的实际像素匹配,但没有运气(甚至检查格式是否正确,它是否正确)。

我做错了什么?有没有更好的方法呢?或者我的紫外线故事是错误的,可以解决 比平铺之前切割源矩形要简单得多吗?

感谢您的时间,

Lennard Fonteijn

更新#1

我实际上设法使用以下来自字节数组的转换将像素数据导出到Bitmap:

int pixel = (buffer[0] & 0xFF) | ((buffer[1] & 0xFF) << 8) | ((buffer[2] & 0xFF) << 16) | ((255 - buffer[3] & 0xFF) << 24);

所以数据看起来并不像我想象的那么虚假。然而,我的下一个问题是抓取源矩形中指定的像素并将它们复制到新纹理。我试图剪切的图像是150x150,但由于某种原因,它被拉伸到256x256图像(2的幂),但是当实际尝试访问超过150x150的像素时,它会抛出OutOfBounds异常。此外,当我实际尝试创建大小为256x256的第二个空白纹理时,无论我放入什么,它都会变成完全黑色。

这是我目前的代码:

//Texture texture = 150x150
DataRectangle dataRectangle = texture.LockRectangle(0, LockFlags.None);
SurfaceDescription surface = texture.GetLevelDescription(0);

Texture texture2 = new Texture(_graphicsDevice, surface.Width, surface.Height, 0, surface.Usage, surface.Format, surface.Pool);
DataRectangle dataRectangle2 = texture2.LockRectangle(0, LockFlags.None);

for (int k = sourceX; k < sourceHeight; k++)
{
    for (int l = sourceY; l < sourceWidth; l++)
    {
        byte[] buffer = new byte[4];
        dataRectangle.Data.Seek((k * dataRectangle.Pitch) + (l* 4), SeekOrigin.Begin);
        dataRectangle.Data.Read(buffer, 0, 4);

        dataRectangle2.Data.Seek(((k - sourceY) * dataRectangle2.Pitch) + ((l - sourceX) * 4), SeekOrigin.Begin);
        dataRectangle2.Data.Write(buffer, 0, 4);
    }
}

sprite.Texture.UnlockRectangle(0);
texture2.UnlockRectangle(0);

_graphicsDevice.SetTexture(0, texture2);

所以我的新(附加)问题是:如何将像素从一个纹理移动到另一个较小的纹理,包括Alpha通道?当我的原始纹理是150x150时,SurfaceDescription为什么报告256x256?

1 个答案:

答案 0 :(得分:5)

回答我自己的问题有点尴尬,但经过一些挖掘和简单的反复试验后,我找到了解决方案。

起初,我不得不改变加载纹理的方式。为了防止它在内部调整大小为2的大小,我不得不使用以下方法:

Texture texture = Texture.FromFile(_graphicsDevice, [filePath], D3DX.DefaultNonPowerOf2, D3DX.DefaultNonPowerOf2, 1, Usage.None, Format.Unknown, Pool.Managed, Filter.None, Filter.None, 0);

请注意我是如何具体指定大小是非二次幂的。

接下来,我的新纹理定义出现了错误。我不必指定0级(并使其自动生成MipMap),而是必须指定1级,如下所示:

Texture texture2 = new Texture(_graphicsDevice, [sourceWidth], [sourceHeight], 1, surface.Usage, surface.Format, surface.Pool);

完成后,我在实际问题中得到的for循环,工作正常:

DataRectangle dataRectangle = texture.LockRectangle(0, LockFlags.None);
SurfaceDescription surface = texture.GetLevelDescription(0);

DataRectangle dataRectangle2 = texture2.LockRectangle(0, LockFlags.None);

for (int y = [sourceX]; y < [sourceHeight]; k++)
{
    for (int x = [sourceY]; x < [sourceWidth]; l++)
    {
        byte[] buffer = new byte[4];
        dataRectangle.Data.Seek((y * dataRectangle.Pitch) + (x * 4), SeekOrigin.Begin);
        dataRectangle.Data.Read(buffer, 0, 4);

        dataRectangle2.Data.Seek(((y - [sourceY]) * dataRectangle2.Pitch) + ((x - [sourceX]) * 4), SeekOrigin.Begin);
        dataRectangle2.Data.Write(buffer, 0, 4);
    }
}

texture.UnlockRectangle(0);
texture2.UnlockRectangle(0);

_graphicsDevice.SetTexture(0, texture2);

括号中的所有内容都被视为此代码段外部的变量。我想_graphicsDevice足够清楚了。我知道.Seek可以简化,但我认为它可以用于示例目的。请注意,我不建议每帧都进行这种操作,因为如果使用错误,它会非常快地耗尽你的FPS。

我花了很长时间才弄明白,但结果令人满意。我要感谢所有瞥见这个问题并试图帮助我的人。

Lennard Fonteijn