SlimDX / DirectX9 / C # - Как получить доступ к пиксельным данным в текстуре

Это мой первый вопрос в StackOverflow, ура! Я могу честно сказать, что использую StackOverflow ежедневно для решения тайн как в своей работе, так и в личном программировании. В 99,9% случаев я на самом деле нахожу здесь нужный ответ, и это здорово!

Моя текущая проблема на самом деле немного озадачила меня, так как я не могу найти ничего, что действительно работает. Я уже прочитал несколько сообщений на GameDev.net и нашел другие ресурсы в сети, но не могу разобраться.

Я занимаюсь портированием небольшого 2D-движка, который написал для XNA, на 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, фактическому пикселю в изображении, но безуспешно (даже проверил, был ли формат правильным, что так и есть).

Что я делаю неправильно? Есть ли способ лучше? Или моя УФ-история ошибочна, и можно ли ее решить намного проще, чем вырезать исходный прямоугольник перед тем, как выложить его мозаикой?

Спасибо за уделенное время,

Леннард Фонтейн

Обновление №1

Мне действительно удалось экспортировать данные пикселей в Bitmap, используя следующее преобразование из массива байтов:

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

Так что данные не кажутся такими фальшивыми, как я думал. Однако моя следующая проблема - захватить пиксели, указанные в исходном прямоугольнике, и скопировать их в новую текстуру. Изображение, которое я пытаюсь вырезать, имеет размер 150x150, но по какой-то причине оно растянуто до изображения 256x256 (степень двойки), но при фактической попытке получить доступ к пикселям за пределами 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);

Итак, мои новые (дополнительные) вопросы: как я могу перемещать пиксели с одной текстуры на другую меньшую текстуру, включая альфа-канал? Любой, почему SurfaceDescription сообщает 256x256, когда моя исходная текстура 150x150?


person Lennard Fonteijn    schedule 11.08.2011    source источник
comment
Я думаю, что это действительно должно быть размещено на gamedev.stackexchange.com. Любой модератор может смело переместить его туда, если это правда.   -  person Lennard Fonteijn    schedule 12.08.2011
comment
Это хороший вопрос - и по теме здесь ... но да, там вам могут помочь.   -  person jcoder    schedule 12.08.2011
comment
Я обновил вопрос новой информацией.   -  person Lennard Fonteijn    schedule 12.08.2011


Ответы (1)


Неловко отвечать на свой вопрос, но, покопавшись еще немного и с помощью простых проб и ошибок, я нашел решение.

Сначала мне пришлось изменить способ загрузки текстуры. Чтобы предотвратить внутреннее изменение размера до размера, равного степени двойки, мне пришлось использовать следующий метод:

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.

Мне потребовалось некоторое время, чтобы разобраться в этом, но результат удовлетворительный. Я хотел бы поблагодарить всех, кто взглянул на вопрос и попытался мне помочь.

Леннард Фонтейн

person Lennard Fonteijn    schedule 12.08.2011