5

这是我在 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 获得的缓冲区是否与图像中的实际像素匹配,但没有运气(甚至检查了格式是否正确,它是正确的)。

我究竟做错了什么?有更好的方法吗?或者我的 UV 故事是错误的,它是否可以比在平铺之前切割源矩形更简单?

感谢您的时间,

伦纳德·方泰因

更新#1

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

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?

4

1 回答 1

5

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

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

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。

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

伦纳德·方泰因

于 2011-08-12T19:02:52.343 回答