Загрузка...
 
Печать

DirectX Graphics. Текстурирование

Рис.1 Текстурирование позволяет наносить на полигоны любые изображения
Рис.1 Текстурирование позволяет наносить на полигоны любые изображения

Полигоны, окрашенные в сплошные цвета смотрятся абстрактно. Отличный способ повысить реализм 3D-сцены - применить к объектам текстуры1. Техника нанесения на полигоны растровых (bitmap) изображений называется текстурирование (texture-mapping). Bitmap-изображения здесь называются текстурами или текстурными картами (См. Рис.1).
При текстурировании программер назначает каждой вершине полигона дополнительные пары координат (т.н. UV-координаты), каждая пара которых определяет точку на текстуре. UV-координаты аналогичны XY-координатам текстуры, с той лишь разницей, что UV-координаты основаны на ширине (width) и высоте (height) текстуры и варьируются в диапазоне от 0.0 до 1.0 .
В обычном программировании X и Y координаты изображения варьируются от 0 до ширины или высоты изображения. Т.е. у изображения размером 640х480 координата X будет варьироваться от 0 до 639, а координата Y от 0 до 479. Для доступа к координатам средней точки изображения в этом случае указывают координаты X=319 и Y=239.
Координаты U и V отсчитываются от 0 (начиная с верхнего или левого края изображения). Для доступа к средней точке (расположенной посередине) любого изображения используют координаты U=0.5 и V=0.5 (См. Рис.2).

Закрыть
noteЛюбопытно

Есть такой трюк. Если присвоить U или V координатам значения больше 1.0, то текстура выйдет за свои пределы, "обернув" полигоны вокруг. Например если координате U присвоить значение 2.0, то текстура прорисуется дважды (стыкуясь одна к другой) по горизонтали. Значение 3.0 означает, что текстура прорисуется трижды. Аналогично работает "перебор" с координатами для V-координаты.

Рис.2 Наложение текстуры на грань с использованием UV текстурных координат
Рис.2 Наложение текстуры на грань с использованием UV текстурных координат

Присвоение вершинам UV-координат текстуры, вместо привычных XY, может сперва показаться излишним. Но на деле это отличное подспорье, т.к. мы можем быстро сменять (swap) текстуры разных размеров, не заботясь об их реальных метрических данных.
Вообще текстурой может быть любое цифровое изображение. Многие игровые движки поддерживают текстуры форматов JPG, GIF, PNG и многих других. В нашем случае мы будем использовать классический вариант - текстуры в формате растровых изображений (BMP). Многие современные видеокарты поддерживают т.н. бамп-мэпинг (bump-mapping) - технологию, которая выявляет на изображении "неровности" (тёмные участки) и генерирует на их основе шероховатую поверхность (с выбоинами), придавая полигональным 3D-объектам ещё более реалистичный вид.

Текстурирование в Direct3D (Using Texture-Mapping with Direct3D; DirectX 8)

Текстуры в Direct3D контролирует специальный объект IDirect3DTexture8, содержащий в себе информацию о текстуре, и предоставляющий доступ к ней (включая указатель на данные пикселя (pixel data) изображения текстуры).
Direct3D (а также производители компьютерного железа) накладывают ряд ограничений на использование текстур. Во-первых, ограничены максимальные размеры текстуры, которые при этом обязательно должны увеличиваться в квадратичной прогрессии (т.е. каждое новое значение - это предыдущее значение, возведённое в квадрат: 8, 32, 128, 256 и т.д.). Многие видеокарты (образца начала 2000-х годов) вообще признают только равносторонние (=квадратные) текстуры (32х32, 64х64 и т.д.). По этой причине старайся всегда использовать только равносторонние текстуры. Они хоть всегда будут поддерживаться на компьютере игрока. По этой же причине многие игры эпохи первого Half Life поддерживали текстуры размером не более 256х256 пикселей. Современные видеокарты "тянут" куда более крупные текстуры. Но без крайней необходимости размеры текстур лучше оставлять минимальными.
Не используй слишком много текстур. В то время как процесс рендеринга уже затекстурированного полигона прост и быстр, подготовка текстуры к использованию может потребовать много ресурсов. Всякий раз, когда игре нужна новая текстура, Direct3D и видеокарта должны слегка потрудиться, чтобы сперва подготовить её к рендерингу.

Закрыть
noteСовет

Многие игрокодеры старой школы для снижения времени, затрачиваемого на подготовку текстуры к рендерингу, просто комбинировали ("склеивали") несколько текстур в одну большую. Такой подход обеспечивал подготовку всех текстур "в один проход". Затем движок выбирал области на большой текстуре по мере необходимости.

Процесс подготовки текстуры к рендерингу включает в себя:

  • копирование текстуры в подходящую память (ОЗУ или видеопамять);
  • назначение текстуре цветового формата, совпадающего с экранным (впрочем, не всегда).

Всё это занимает драгоценное процессорное время. И чем меньше комп этим занимается, тем лучше.

Загрузка текстуры (Loading a Texture; DirectX 8)

На начальном этапе файл изображения считывается с жёсткого диска или другого носителя. Уже знакомая нам вспомогательная библиотека D3DX содержит набор функций для загрузки и управления текстурами:

Функция Описание
D3DXCreateTextureFromFile Загружает изображение текстуры из растрового (bitmap) файла на диске.
D3DXCreateTextureFromFileEx Более продвинутая версия функции D3DXCreateTextureFromFile.
D3DXCreateTextureFromFileInMemory Загружает текстурное изображение из файла, который уже загружен в память.
D3DXCreateTextureFromFileInMemoryEx Более продвинутая версия функции D3DXCreateTextureFromFileInMemory.
D3DXCreateTextureFromResource Загружает текстурное изображение из файла двоичных ресурсов.
D3DXCreateTextureFromResourceEx Более продвинутая версия функции D3DXCreateTextureFromResource.

Видим, что каждая из трёх функций представлена в двух версиях:

  • одна для быстрой загрузки текстур безо всяких "наворотов",
  • другая (с окончанием Ex) более продвинута и даёт бОльший контроль над процессом загрузки текстуры.

Начнём с применения функции D3DXCreateTextureFromFile. Вот её прототип:

Прототип функции D3DXCreateTextureFromFile
HRESULT D3DXCreateTextureFromFile(
 IDirect3DDevice8 *pDevice,  // Предварительно проинициализированный объект устройства Direct3D.
 LPCSTR pSrcFile,  // Имя файла растрового изображения.
 IDirect3DTexture8 **ppTexture);  // Имя создаваемого объекта текстуры.

Функция не сложная. Указываем объект устройства Direct3D, с которым работаем, имя файла текстуры и указатель на объект, в который текстура будет сохранена.
Вот пример применения (загружаем текстуру из файла texture.bmp)

// g_pD3DDevice - предварительно проинициализированный объект устройства Direct3D.
IDirect3DTexture8 *pD3DTexture;
if(FAILED(D3DXCreateTextureFromFile(g_pD3DDevice, "texture.bmp", (void**)&pD3DTexture)))
{
 // Ошибка.
}

Функция сама проделывает всю инициализацию и "стыкует" текстуру к классу D3DPOOL_MANAGED, сохраняя её в памяти. Однажды созданная и сохранённая в памяти текстура может использоваться сколько угодно раз. По окончании работы с ней текстуру удаляют. Неудалённые (т.н. "утерянные") текстуры являлись головной болью для всех, кто работал с ними в более ранних версиях DirectX SDK.

Рис.3 Каждый текстурный уровень изменяет выходной пиксель разными способами. Здесь входной пиксель проходит через несколько стадий преобразований.
Рис.3 Каждый текстурный уровень изменяет выходной пиксель разными способами. Здесь входной пиксель проходит через несколько стадий преобразований.

Назначение текстуры полигону. Текстурные уровни (Setting the Texture. Texture Stages; DirectX 8)

Как было сказано выше, объект устройства Direct3D перед рендерингом текстуры должен сперва сам себя подготовить. Если точнее, то подготовка идёт перед рендерингом полигона, с нанесённой на него текстурой. Если у тебя 1000 полигонов, каждый из которых использует свою уникальную текстуру, необходимо пройти цикл такой подготовки для каждого полигона. И уже затем каждому из них назначается текстура и всё это рендерится. Весь процесс повторяется для каждого полигона. Если несколько полигонов используют одну и ту же текстуру, то эффективнее будет организовать цикл "назначение текстуры - рендеринг всех полигонов, которые её используют".
Для назначения текстуры используется функция IDirect3DDevice8::SetTexture. Вот её прототип:

Прототип функции IDirect3DDevice8::SetTexture
HRESULT IDirect3DDevice8::SetTexture(
 DWORD Stage,  // "Сцена" текстуры (0-7)
 IDirect3DBaseTexture8 *pTexture);  // Объект текстуры, который назначается.

Первый параметр представляет собой т.н. текстурный уровень (texture stage). Применяется при мультитекстурировании и является одной из самых крутых фишек Direct3D. Каждая из максимально возможных восьми текстур может быть помещена в один из этих уровней. То есть, в первый уровень мы, допустим, устанавливаем исходную текстуру, а во второй ту, что будем накладывать сверху. Первый параметр этой функции как раз и указывает номер уровня, в который необходимо поместить текстуру.
Текстурирование в Direct3D гибко настраивается. Текстура необязательно должна загружаться из одного источника. Таких источников может быть от 1 до 8. Они и называются текстурными уровнями (См. Рис.3).
Во время рендеринга полигона для каждого пикселя текстуры Direct3D начинает поочерёдно опрашивать все 8 уровней, начиная с первого (0), на предмет наличия текстурного пикселя. Если пиксель обнаружен на первом уровне, то либо происходит переход на следующий, либо модифицируется текстурный пиксель на предыдущем. Данный процесс продолжается, пока не будут опрошены все 8 текстурных уровня.
На каждом текстурном уровне программер может изменять текстурный пиксель. Например, можно включить смешивание (blending) пикселя с предыдущего уровня с пикселем следующего. Можно увеличить/уменьшить насыщенность, светлость (brightness) и даже применить специальный эффект, известный как альфа-смешивание (alpha-blending; техника, которая смешивает цвета нескольких пикселей).

Закрыть
noteЛюбопытно

Забегая вперёд скажем, что на основе мультитекстурирования построена технология бамп-мэпинга (bump-mapping(external link)).

Такой подход даёт широкие возможности по воплощению самых смелых текстурных замыслов. В данной статье, для простоты изложения, мы будем использовать всего 1 текстурный уровень, который:

  • просто выделяет цвет пикселя из текстуры,
  • применяет к текстурному пикселю инфу о цветах вершин полигона,
  • рендерит окрашенный пиксель, нанесённым на полигон.

Следующий фрагмент кода устанавливает текстуру на текстурный уровень 0 и указывает рендереру получить из него текстурный пиксель, применить к нему информацию о цветах вершин полигона и отключает альфа-смешивание (alpha-blending):

// g_pD3DDevice - предварительно проинициализированный объект устройства Direct3D.
// pD3DTexture - предварительно созданный и проинициализированный объект текстуры.

// Устанавливаем текстуру на текстурный уровень 0.
g_pD3DDevice->SetTexture(0, pTexture);

// Устанавливаем параметры текстурного уровня (необходимо проделать всего 1 раз).
g_pD3DDevice->SetTextureStageState(0, D3DTSS_COLOROP, D3DTOP_MODULATE);
g_pD3DDevice->SetTextureStageState(0, D3DTSS_COLORARG1, D3DTA_TEXTURE);
g_pD3DDevice->SetTextureStageState(0, D3DTSS_COLORARG2, D3DTA_DIFFUSE);
g_pD3DDevice->SetTextureStageState(0, D3DTSS_ALPHAOP, D3DTOP_DISABLE);

Это лишь самый базовый набор операций с текстурой. Сама текстура устанавливается на данный текстурный уровень лишь однажды. Более подробная информация по использованию текстурных уровней как всегда есть в документации к DirectX SDK.
По окончании использования текстуры (после завершения рендеринга полигона) снова вызываем функцию SetTexture, указывая во втором параметре NULL:

g_pD3DDevice->SetTexture(0, NULL);

Это удалит текстуру из памяти. Пропуск данной операции грозит утечками памяти (memory leaks) и последующей нестабильной работой игрового приложения.

Применение текстурных фильтров (Using Texture Filters; DirectX 8)

Фильтрация текстур вступает в игру при рендеринге уже текстурированных полигонов. Т.к. разрешение экрана ограничено, выводимое изображение с полигонами может иметь различные нежелательные артефакты (зубчатые грани при прорисовке диагональных линий; пикселизованные (чрезмерно увеличенные) фрагменты изображения текстуры, появляющиеся при увеличении объекта/приближении камеры).
Для этих (и многих других) косяков были придуманы текстурные фильтры. Direct3D применяет ряд фильтров, заметно улучшающих вид уже отрендеренного изображения.
Для применения фильтра вызывают специальную функцию IDirect3DDevice8::SetTextureStageState. Вот её прототип:

Прототип функции IDirect3DDevice8::SetTextureStageState
HRESULT IDirect3DDevice8::SetTextureStageState(
 DWORD Stage,  // Текстурный уровень (0-7).
 D3DTEXTURESTAGETYPE Type,  // Состояние (state) уровня.
 DWORD Value);  // Применяемое значение.

И вновь видим активное применение текстурных уровней. Но второй и третий параметры здесь уже другие.
Параметр Type представляет собой состояние (state) текстурного уровня, с которым в данный момент работаем. В нашем случае возьмём одно из двух значений: D3DTSS_MAGFILTER (для увеличения масштаба текстуры прямо на полигоне) или D3DTSS_MINFILTER (для уменьшения текстуры на полигоне). Оба этих состояния определяют, как Direct3D будет смешивать прилегающие пиксели внутри текстуры (прежде чем вывести пиксель на экран).
Параметр Value может принимать одно из следующих значений:

Значение Описание
D3DTEXF_NONE Не применять фильтр.
D3DTEXF_POINT Самый быстрый алгоритм фильтрации. Использует цвет одного пикселя для создания текстурной карты.
D3DTEXF_LINEAR Метод билинейной интерполяции (bilinear interpolation). Комбинирует 4 пикселя текстурной карты для получения одного пикселя смешанного цвета. Тоже оч. быстрый метод текстурирования, придающий текстуре хороший, сглаженный вид.
D3DTEXF_ANISOTROPIC Метод анизотропной (anisotropic) фильтрации. Учитывает угол между плоскостью экрана и текстурированного полигона. Выглядит хорошо, но работает относительно медленно.

На деле обычно применяют всего 2 режима фильтрации: D3DTEXF_POINT и D3DTEXF_LINEAR. Вот пример использования фильтрации текстуры:

// g_pD3DDevice - предварительно проинициализированный объект устройства Direct3D.

// Назначаем фильтр увеличения (magnification filter).
if(FAILED(g_pD3DDevice->SetTextureStageState(0, D3DTS_MAGFILTER, D3DTEXF_POINT)))
{
  // Ошибка.
}

// Назначаем фильтр уменьшения (minification filter).
if(FAILED(g_pD3DDevice->SetTextureStageState(0, D3DTS_MINFILTER, D3DTEXF_POINT)))
{
  // Ошибка.
}

Рендеринг текстурированных объектов (Rendering Textured Objects; DirectX 8)

Прежде чем рендерить объект (или группу полигонов) с текстурой, необходимо убедиться, что вершины полигона содержат пары координат UV. Кастомная FVF-структура, как правило, содержит набор 3D-координат вершины и текстурные координаты в таком виде:

typedef struct
{
 D3DVECTOR3 Position;  // Вектор позиции вершины.
 float tu, tv;   // Здесь добавляем текстурные координаты.
}sVertex;

Теперь можно создавать свой FVF-формат для информирования Direct3D об используемых компонентах вершины. На данном этапе здесь задействованы нетрансформированные 3D-координаты вершины и пара текстурных координат (tv и tu).
Далее определим нашу FVF-структуру так:

#define VERTEXFMT (D3DFVF_XYZ | D3DFVF_TEX1)

Теперь можно выводить графику на экран. Путём добавления всего нескольких строк кода мы существенно расширили функцию простой отрисовки полигона, которая теперь включает в себя текстурные координаты. Предположим, ты проинициализировал объект устройства Direct3D, определил (defined) вершинный буфер (vertex buffer) с информацией о текстуре, а также установил каждую из трёх матриц:

  • мировая матрица (world matrix),
  • матрица вида (view matrix),
  • матрица проекции (projection matrix).

Тогда можно рассмотреть такой пример загрузки текстуры и её применения к полигонам при отрисовке листа треугольников (triangle list):

// g_pD3DDevice - предварительно проинициализированный объект устройства Direct3D.
// NumPolys - число выводимых полигонов.
// g_pD3DVertexBuffer - предварительно созданный вершинный буфер, заполненный инфой о вершинах.
IDirect3DTexture8 *pD3DTexture;  // Объект текстуры.

// Загружаем текстуру.
D3DXCreateTextureFromFile(g_pD3DDevice, "texture.bmp", (void**)&pD3DTexture);

if(SUCCEEDED(g_pD3DDevice->BeginScene()))
{
 // Назначаем текстуру.
 g_pD3DDevice->SetTexture(0, pD3DTexture);

 // Назначаем источник потока (Stream Source) и вершинный шейдер (Vertex Shader).
 g_pD3DDevice->SetStreamSource(0, g_pD3DVertexBuffer, sizeof(sVertex);
 g_pD3DDevice->SetVertexShader(VERTEXFMT);

 // Отрисовываем список треугольников (triangle list).
 g_pD3DDevice->DrawPrimitive(D3DPT_TRIANGLELIST, 0, NumPolys0;

 // Завершаем сцену.
 g_pD3DDevice->EndScene();

 // Освобождаем память с текстурой.
 g_pD3DDevice->SetTexture(0, NULL);
}

Альфа-смешивание (Alpha Blending)

Представь, что ты находишься внутри одного из самых высоких зданий в мире. Подойдя к окну и посмотрев вниз, ты увидишь весь город внизу. Нежно голубой цветовой тон (hue) оконного стекла придаёт всему увиденному умиротворяющий затенённый вид, схожий с тем, что можно наблюдать на утреннем небе в ясную погоду.
А теперь представь ту же самую сцену, переложенную на язык 3D-гарфики. Весь 3D-мир состоит из полигонов, которые (чаще всего) представляют собой "твёрдые" непрозрачные объекты. Ты не можешь посмотреть сквозь них и увидеть, что спрятано за ними. Но что, если в игре понадобится посмотреть в (закрытое) окно? Как насчёт голубого оттенка, который придаёт виду за окном виртуальное "стекло"? Та же история и с т.н. прозрачными щелями (transparent blits), т.е. с полигонами, у которых есть прозрачные участки. Как же вывести частично прозрачный объект, например стену с отверстием в ней? Отверстие абсолютно прозрачно. Даже с учётом того, что стена твёрдая, ты можешь видеть через отверстие другие объекты, которые эта стена скрывает.

Источники:


1. Adams J. Programming Role Playing Games with DirectX 8.0. - Premier Press. 2002

Contributors to this page: slymentat .
Последнее изменение страницы Пятница 17 / Июль, 2020 12:49:41 MSK автор slymentat.

Помочь проекту

Яндекс-деньги: 410011791055108