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

发布于 2024-11-29 02:11:14 字数 2626 浏览 1 评论 0原文

这是我在 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

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

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?

This is my first question ever on StackOverflow, hurray! I can honestly say I use StackOverflow on daily basis for both my work and personal programming mysteries. 99,9% of the time I actually find the answer I need on here too, which is great!

My current problem actually stumped me a little as I can't seem to find anything which actually works. I've already read several posts on GameDev.net and found other resources around the net but can't sort it out.

I am in the process of porting a small 2D engine I wrote for XNA to SlimDX (just DirectX9 at the moment), which has been a good move as I learned more about inner-workings of DirectX in just a few days than I did in six months of working with XNA. I got most of my basic rendering features done and actually managed to recreate the XNA SpriteBatch with a ton of additional features (which I really missed in XNA).

One of the last things I'm trying to get to work is to extract a source-rectangle from a given texture and use it for tiling. Why: When not tiling you can just mess around with the UV to get the source you want to display (eg: 0.3;0.3 to 0.5;0.5), but when tiling you need the UV to tile (0;0 to 2;2 means tile images twice) and therefore need a cutout texture.

To make a long story short, I try to use the following:

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);

I tried different pixels but all seem to give bogus data. For example, I actually tried using my current avatar to see if the buffer I got from the DataRectangle matches the actual pixel in the image, but no luck (even checked if the Format was correct, which it is).

What am I doing wrong? Is there a better way to do it? Or is my UV story wrong and can it be solved
much simpler than cutting out a source-rectangle before tiling it?

Thank you for your time,

Lennard Fonteijn

Update #1

I actually managed to export the pixel data to a Bitmap using the following conversion from a byte array:

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

So the data doesn't seem so bogus as I thought it was. My next problem, however, is grabbing the pixels specified in the source rectangle and copy them to a new texture. The image I'm trying to cut is 150x150, but for some reason it is stretched to a 256x256 image (power of two), but when actually trying to access pixels beyond 150x150, it throws an OutOfBounds exception. Also, when I actually try to create a second blank texture of size 256x256, no matter what I put into it, it turns out completely black.

Here's my current code:

//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);

So my new (additional) questions are: How can I move over pixels from one texture to another smaller texture, including the Alpha channel? Any why does the SurfaceDescription report 256x256 when my original texture is 150x150?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

递刀给你 2024-12-06 02:11:14

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

首先,我必须改变加载纹理的方式。为了防止它在内部调整为二次幂大小,我必须使用以下方法:

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

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

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

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。

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

伦纳德·方泰因

It's kind of awkward to answer my own question, but after some more digging and by simple trial and error, I found the solution.

At first, I had to change the way I loaded my texture. To prevent it from internally resizing to a Power-of-Two size, I had to use the following method:

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

Note how I specifically specified that the size is Non-Power-of-Two.

Next, there was a mistake in my new texture definition. Instead of specifying 0 levels (and making it auto-generate a MipMap), I had to specify 1 level, like this:

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

After having done that, the for-loop I have in my actual question, works fine:

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);

Everything in brackets is considered a variable from outside this snippet. I suppose _graphicsDevice is clear enough. I am aware the .Seek can be simplified, but I think it works fine for example purposes. Note that I do not recommended doing this kind of operation every frame, as it drains your FPS quite fast when used wrong.

It took me quite some time to figure it out, but the result is satisfying. I would like to thank everyone who glimpsed over the question and has tried to help me.

Lennard Fonteijn

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文