Введение в программирование трехмерных игр с DX9

         

Знакомство с пиксельными шейдерами


Пиксельный шейдер (pixel shader) — это программа, выполняемая процессором видеокарты во время процесса растеризации для каждого пикселя. (В отличие от вершинных шейдеров, Direct3D не может программно эмулировать пиксельные шейдеры.) Пиксельные шейдеры заменяют этап мультитекстурирования в фиксированном конвейере функций и предоставляют нам возможность непосредственно управлять отдельными пикселями и получить доступ к текстурным координатам для каждого пикселя. Прямой доступ к пикселям и координатам текстуры позволяет нам реализовать множество специальных эффектов, таких как мультитекстурирование, попиксельное освещение, глубина резкости, моделирование облаков, имитация огня и сложные способы затенения.

Вы можете проверить, какую версию пиксельных шейдеров использует установленная видеокарта, с помощью члена PixelShaderVersion структуры D3DCAPS9 и макроса D3DPS_VERSION. Эта проверка показана в приведенном ниже фрагменте кода:

// Если поддерживаемая устройством версия меньше 2.0 if(caps.PixelShaderVersion < D3DPS_VERSION(2, 0)) // Значит пиксельные шейдеры версии 2.0 устройство не поддерживает

Цели

Получить базовое представление о концепции мультитекстурирования.

Узнать как написать, создать и использовать пиксельный шейдер.

Узнать реализовать мультитекстурирование с помощью пиксельного шейдера.



Пиксельные шейдеры заменяют этап мультитекстурирования

Пиксельные шейдеры заменяют этап мультитекстурирования в фиксированном конвейере функций. Более того, пиксельные шейдеры предоставляют возможность изменять цвет каждого отдельного пикселя любым способом и предоставляют доступ к данным текстуры, что позворяет реализовать множество специальных эффектов, которые недоступны при работе с фиксированным конвейером функций.
Мультитекстурирование — это процесс одновременного наложения нескольких текстур и их смешивания для получения желаемого результата. Мультитекстурирование обычно используется для реализации всего механизма освещения статических объектов.
Встроенный в HLSL объект sampler идентифицирует конкретный этап текстурирования/выборки. Объект sampler используется для ссылки на этап текстурирования/выборки из пиксельного шейдера.
ПРИМЕЧАНИЕ
Как только вы разберетесь с принципами работы вершинных и пиксельных шейдеров, у вас появится множество идей об эффектах, которые можно реализовать с их помощью. Наилучший способ генерации идей, которые могут быть реализованы с помощью вершинных и пиксельных шейдеров — изучение уже реализованных эффектов. Изданная Wordware Publishing книга «Direct3D ShaderX: Vertex and Pixel Shader Tips and Tricks», редактором которой является Вольфганг Энджел, является хорошей отправной точкой. Кроме того следует посетить сайты для разработчиков Nvidia и ATI, расположенные по адресам http://developer.nvidia.com/ и http://ati.com/developer/index.html, соответственно. Также рекомендуем вам книгу Рэндайма Фернандо и Марка Дж. Килгарда «CG: The Cg Tutorial». Она является замечательным учебником по программированию трехмерной графики с использованием высокоуровневого языка графического программирования Cg, который во многом похож на Direct3D HLSL.

Этапы работы с пиксельным шейдером


Приведенный ниже список перечисляет действия, которые необходимо выполнить для создания и использования пиксельного шейдера.

Написать и скомпилировать пиксельный шейдер.



Создать представляющий пиксельный шейдер интерфейс IDirect3DPixelShader9 на основании скомпилированного кода шейдера.

Установить пиксельный шейдер с помощью метода IDirect3DDevice9::SetPixelShader.

Конечно же, после завершения работы нам надо уничтожить пиксельный шейдер. В следующих разделах детально рассмотрены все эти этапы.



Комбинирование текстур Пусть b



Рисунок 18.2. Комбинирование текстур. Пусть b, s и t — это цвета соответствующих текселей из текстуры ящика, текстуры прожектора и текстуры текста соответственно. Тогда цвет их комбинации определяется по формуле c = b 

 s + t, где
обозначает покомпонентное умножение


Данный пример может быть реализован и без пиксельных шейдеров. Однако, это более простой и прямолинейный способ реализации, позволяющий к тому же продемонстрировать написание, создание и использование пиксельных шейдеров без отвлечения на реализацию алгоритма какого-нибудь специального эффекта.

Хотя в рассматриваемом примере мы используем одновременно только три текстуры, весьма полезно узнать сколько объектов выборки могут одновремено использоваться в каждой из версий пиксельных шейдеров. Другими словами, как количество одновременно используемых текстур зависит от используемой версии пиксельных шейдеров.

Пиксельные шейдеры версий от ps_1_1 до ps_1_3 поддерживают до четырех выборок текстуры.

Пиксельные шейдеры версии ps_1_4 поддерживают до шести выборок тектсуры.

Пиксельные шейдеры версий от ps_2_0 до ps_3_0 поддерживают до 16 выборок текстуры.

Код пиксельного шейдера для реализации мультитекстурирования с использованием трех текстур выглядит следующим образом:

// // Файл : ps_multitex.txt // Описание: Пиксельный шейдер, выполняющий мультитекстурирование //

// // Глобальные переменные //

sampler BaseTex; sampler SpotLightTex; sampler StringTex;

// // Структуры //

struct PS_INPUT { float2 base : TEXCOORD0; float2 spotlight : TEXCOORD1; float2 text : TEXCOORD2; };

struct PS_OUTPUT { vector diffuse : COLOR0; };

// // Точка входа //

PS_OUTPUT Main(PS_INPUT input) { // Обнуляем члены выходной структуры PS_OUTPUT output = (PS_OUTPUT)0;

// Выборка данных из соответствующих текстур vector b = tex2D(BaseTex, input.base); vector s = tex2D(SpotLightTex, input.spotlight); vector t = tex2D(StringTex, input.text);

// Комбинирование цветов текселей vector c = b * s + t;


// Слегка увеличиваем яркость пикселя c += 0.1f;

// Сохраняем результатирующий цвет output.diffuse = c;

return output; }

Сперва в пиксельном шейдере мы объявляем три объекта выборки — по одному для каждой, участвующей в смешивании текстуры. Затем мы описываем входную и выходную структуры. Обратите внимание, что в пиксельный шейдер не передается никаких значений цветов; это вызвано тем, что для текстурирования и освещения объекта применяются только текстуры. Так, BaseTex хранит цвета нашей поверхности, а SpotLightTex — карту освещения. Пиксельный шейдер возвращает единственное значение цвета, которое определяет вычисленный нами цвет данного пикселя.

Функция Main выполняет выборку значений для трех текстур с помощью функции tex2D. Таким образом, мы получаем отображаемые на обрабатываемый в данный момент пиксель тексели каждой из текстур, определяемые на основе заданных координат текстуры и объекта выборки. Затем мы комбинируем цвета текселей согласно формуле c = b * s + t. После этого мы слегка осветляем полученный пиксель, добавляя 0.1f к каждой из его компонент. И, наконец, мы сохраняем цвет полученного в результате пикселя и взвращаем его.

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

IDirect3DPixelShader9* MultiTexPS = 0; ID3DXConstantTable* MultiTexCT = 0;

IDirect3DVertexBuffer9* QuadVB = 0;

IDirect3DTexture9* BaseTex = 0; IDirect3DTexture9* SpotLightTex = 0; IDirect3DTexture9* StringTex = 0;

D3DXHANDLE BaseTexHandle = 0; D3DXHANDLE SpotLightTexHandle = 0; D3DXHANDLE StringTexHandle = 0;

D3DXCONSTANT_DESC BaseTexDesc; D3DXCONSTANT_DESC SpotLightTexDesc; D3DXCONSTANT_DESC StringTexDesc;

Структура данных вершины для примера мультитекстурирования выглядит следующим образом:

struct MultiTexVertex { MultiTexVertex(float x, float y, float z, float u0, float v0, float u1, float v1, float u2, float v2) { _x = x; _y = y; _z = z; _u0 = u0; _v0 = v0; _u1 = u1; _v1 = v1; _u2 = u2, _v2 = v2; }



float _x, _y, _z; float _u0, _v0; float _u1, _v1; float _u2, _v2;

static const DWORD FVF; }; const DWORD MultiTexVertex::FVF = D3DFVF_XYZ | D3DFVF_TEX3;

Обратите внимание, что она содержит три набора координат текстур.

Функция Setup выполняет следующие действия:

Заполняет вершинный буфер данными квадрата.

Компилирует пиксельный шейдер.

Создает пиксельный шейдер.

Загружает текстуры.

Устанавливает матрицу проекции и отключает освещение.

Получает дескрипторы объектов выборки.

Получает описания объектов выборки.

bool Setup() { HRESULT hr = 0;

// // Создание квадрата //

Device->CreateVertexBuffer( 6 * sizeof(MultiTexVertex), D3DUSAGE_WRITEONLY, MultiTexVertex::FVF, D3DPOOL_MANAGED, &QuadVB, 0);

MultiTexVertex* v = 0; QuadVB->Lock(0, 0, (void**)&v, 0);

v[0] = MultiTexVertex(-10.0f, -10.0f, 5.0f, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f, 1.0f); v[1] = MultiTexVertex(-10.0f, 10.0f, 5.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f); v[2] = MultiTexVertex( 10.0f, 10.0f, 5.0f, 1.0f, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f);

v[3] = MultiTexVertex(-10.0f, -10.0f, 5.0f, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f, 1.0f); v[4] = MultiTexVertex( 10.0f, 10.0f, 5.0f, 1.0f, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f); v[5] = MultiTexVertex( 10.0f, -10.0f, 5.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f);

QuadVB->Unlock();

// // Компиляция шейдера //

ID3DXBuffer* shader = 0; ID3DXBuffer* errorBuffer = 0;

hr = D3DXCompileShaderFromFile( "ps_multitex.txt", 0, 0, "Main", // имя точки входа "ps_1_1", D3DXSHADER_DEBUG, &shader, &errorBuffer, &MultiTexCT);

// Выводим любые сообщения об ошибках if(errorBuffer) { ::MessageBox(0, (char*)errorBuffer->GetBufferPointer(), 0, 0); d3d::Release<ID3DXBuffer*>(errorBuffer); }

if(FAILED(hr)) { ::MessageBox(0, "D3DXCompileShaderFromFile() - FAILED", 0, 0); return false; }

// // Создание пиксельного шейдера //

hr = Device->CreatePixelShader( (DWORD*)shader->GetBufferPointer(), &MultiTexPS);

if(FAILED(hr)) { ::MessageBox(0, "CreateVertexShader - FAILED", 0, 0); return false; }



d3d::Release<ID3DXBuffer*>(shader);

// // Загрузка текстур //

D3DXCreateTextureFromFile(Device, "crate.bmp", &BaseTex); D3DXCreateTextureFromFile(Device, "spotlight.bmp", &SpotLightTex); D3DXCreateTextureFromFile(Device, "text.bmp", &StringTex);

// // Установка матрицы проекции //

D3DXMATRIX P; D3DXMatrixPerspectiveFovLH( &P, D3DX_PI * 0.25f, (float)Width / (float)Height, 1.0f, 1000.0f);

Device->SetTransform(D3DTS_PROJECTION, &P);

// // Запрещение освещения //

Device->SetRenderState(D3DRS_LIGHTING, false);

// // Получение дескрипторов //

BaseTexHandle = MultiTexCT->GetConstantByName(0, "BaseTex"); SpotLightTexHandle = MultiTexCT->GetConstantByName(0, "SpotLightTex"); StringTexHandle = MultiTexCT->GetConstantByName(0, "StringTex");

// // Получение описания констант //

UINT count; MultiTexCT->GetConstantDesc( BaseTexHandle, &BaseTexDesc, &count); MultiTexCT->GetConstantDesc( SpotLightTexHandle, &SpotLightTexDesc, &count); MultiTexCT->GetConstantDesc( StringTexHandle, &StringTexDesc, &count);

MultiTexCT->SetDefaults(Device);

return true; }

Функция Display устанавливает пиксельный шейдер, разрешает использование трех текстур и устанавливает для них требуемые режимы выборки перед визуализацией квадрата.

bool Display(float timeDelta) { if(Device) { // // ...код обновления камеры пропущен //

// // Визуализация //

Device->Clear(0, 0, D3DCLEAR_TARGET | D3DCLEAR_ZBUFFER, 0xffffffff, 1.0f, 0); Device->BeginScene();

// Установка пиксельного шейдера Device->SetPixelShader(MultiTexPS); Device->SetFVF(MultiTexVertex::FVF); Device->SetStreamSource(0, QuadVB, 0, sizeof(MultiTexVertex));

// Базовая текстура Device->SetTexture(BaseTexDesc.RegisterIndex, BaseTex); Device->SetSamplerState(BaseTexDesc.RegisterIndex, D3DSAMP_MAGFILTER, D3DTEXF_LINEAR); Device->SetSamplerState(BaseTexDesc.RegisterIndex, D3DSAMP_MINFILTER, D3DTEXF_LINEAR); Device->SetSamplerState(BaseTexDesc.RegisterIndex, D3DSAMP_MIPFILTER, D3DTEXF_LINEAR);



// Текстура прожектора Device->SetTexture(SpotLightTexDesc.RegisterIndex, SpotLightTex); Device->SetSamplerState(SpotLightTexDesc.RegisterIndex, D3DSAMP_MAGFILTER, D3DTEXF_LINEAR); Device->SetSamplerState(SpotLightTexDesc.RegisterIndex, D3DSAMP_MINFILTER, D3DTEXF_LINEAR); Device->SetSamplerState(SpotLightTexDesc.RegisterIndex, D3DSAMP_MIPFILTER, D3DTEXF_LINEAR);

// Текстура с текстом Device->SetTexture( StringTexDesc.RegisterIndex, StringTex); Device->SetSamplerState(StringTexDesc.RegisterIndex, D3DSAMP_MAGFILTER, D3DTEXF_LINEAR); Device->SetSamplerState(StringTexDesc.RegisterIndex, D3DSAMP_MINFILTER, D3DTEXF_LINEAR); Device->SetSamplerState(StringTexDesc.RegisterIndex, D3DSAMP_MIPFILTER, D3DTEXF_LINEAR);

// Рисуем квадрат Device->DrawPrimitive(D3DPT_TRIANGLELIST, 0, 2);

Device->EndScene(); Device->Present(0, 0, 0, 0); } return true; }

И, конечно, следует помнить о необходимости освобождения полученных интерфейсов в функции Cleanup:

void Cleanup() { d3d::Release<IDirect3DVertexBuffer9*>(QuadVB);

d3d::Release<IDirect3DTexture9*>(BaseTex); d3d::Release<IDirect3DTexture9*>(SpotLightTex); d3d::Release<IDirect3DTexture9*>(StringTex);

d3d::Release<IDirect3DPixelShader9*>(MultiTexPS); d3d::Release<ID3DXConstantTable*>(MultiTexCT); }


Координаты для нескольких текстур


18.1.2. Координаты для нескольких текстур

Вспомните, в главе 6 говорилось, что для каждого трехмерного треугольника мы определяем соответствующий треугольный фрагмент текстуры, который накладывается на трехмерный треугольник. Мы делаем это путем добавления к данным каждой вершины координат текстуры. Таким образом, три вершины определяющие треугольник, определяют и соответствующий треугольный фрагмент текстуры.

Поскольку теперь мы используем несколько текстур, для каждой из трех определяющих треугольник вершин нам требуется определение соответствующего треугольного фрагмента каждой из используемых текстур. Мы делаем это добавляя наборы координат текстуры в данные каждой вершины — по одному набору координат для каждой используемой текстуры. Например, если мы хотим смешивать вместе три текстуры, то у каждой вершины должны быть три набора координат текстуры, индексирующих три используемые текстуры. Таким образом, структура данных вершины для мультитекстурирования с использованием трех текстур может выглядеть так:

struct MultiTexVertex { MultiTexVertex(float x, float y, float z, float u0, float v0, float u1, float v1, float u2, float v2) { _x = x; _y = y; _z = z; _u0 = u0; _v0 = v0; _u1 = u1; _v1 = v1; _u2 = u2; _v2 = v2; }

float _x, _y, _z; float _u0, _v0; // Координаты текстуры накладываемой на этапе 0. float _u1, _v1; // Координаты текстуры накладываемой на этапе 1. float _u2, _v2; // Координаты текстуры накладываемой на этапе 2.

static const DWORD FVF; }; const DWORD MultiTexVertex::FVF = D3DFVF_XYZ | D3DFVF_TEX3;

Обратите внимание на использованный в настраиваемом формате вершин флаг D3DFVF_TEX3, который указывает, что структура вершины содержит три набора координат текстур. Фиксированный конвейер поддерживает до восьми наборов координат текстур. Если этого вам мало, придется использовать объявление вершин и программируемый конвейер.

ПРИМЕЧАНИЕ

В последних версиях пиксельных шейдеров можно использовать один набор координат текстуры для индексирования сразу нескольких текстур, что позволяет убрать дополнительные наборы координат текстур из данных вершины. Конечно, в этом случае подразумевается, что на каждом этапе текстурирования используются одни и те же координаты текстуры. Если координаты текстуры на разных этапах текстурирования различаются, надо использовать несколько наборов координат текстур в данных вершины.

Написание и компиляция пиксельного шейдера


18.3.1. Написание и компиляция пиксельного шейдера

Мы компилируем пиксельный шейдер точно так же, как компилировали вершинные шейдеры. Сперва мы должны написать программу пиксельного шейдера. В этой книге мы пишем наши шейдеры на HLSL. Как только исходный код шейдера написан, мы можем скомпилировать его с помощью функции D3DXCompileShaderFromFile, как описано в разделе16.2. Вспомните, что эта функция возвращает указатель на интерфейс ID3DXBuffer, который содержит скомпилированный код шейдера.

ПРИМЕЧАНИЕ

Поскольку сейчас мы работаем с пиксельными шейдерами, важно помнить о том, что при компиляции необходимо указывать целевую версию пиксельных шейдероы (т.е. ps_2_0), а не целевую версию вершинных шейдеров (т.е. vs_2_0). Целевая версия шейдеров указывается в параметре функции D3DXCompileShaderFromFile. За подробностями обратитесь к разделу 16.2.

Объекты выборки в HLSL

Выборка для текстур в пиксельном шейдере осуществляется с помощью специальных встроенных функций HLSL tex*.

ПРИМЕЧАНИЕ

Под выборкой (sampling) мы понимаем выборку по индексу соответствующего пикселю текселя на основе координат текстуры для пикселя и режимов выборки (режимов фильтрации текстур).

В общем случае эти функции требуют, чтобы мы указали две вещи:

Координаты текстуры (u, v), используемые для индексации элементов текстуры.

Текстуру в которой выполняется индексация.

Координаты текстуры (u, v), как вы помните, передаются во входных данных пиксельного шейдера. Текстура, элемент которой нам надо получить, идентифицируется в пиксельном шейдере с помощью специального объекта HLSL, называемого объект выборки (sampler). Можно думать об объектах выборки как об объектах, идентифицирующих текстуру и этап выборки. Для примера предположим, что мы используем три этапа текстурирования, а значит в пиксельном шейдере нам требуется возможность ссылаться на каждый из этих этапов. Для этого в пиксельном шейдере напишем:

sampler FirstTex; sampler SecondTex; sampler ThirdTex;

Direct3D свяжет каждый из этих объектов выборки с уникальным этапом выборки. Затем в приложении мы определяем этап, которому соответствует объект выборки и устанавливаем для этого этапа требуемые текстуру и режимы выборки. Приведенный ниже код показывает, как приложение может установить текстуру и режимы выборки для FirstTex:

// Создание текстуры IDirect3DTexture9* Tex; D3DXCreateTextureFromFile(Device, "tex.bmp", &Tex); . . . // Получение дескриптора константы FirstTexHandle = MultiTexCT->GetConstantByName(0, "FirstTex");

//Получение описания константы D3DXCONSTANT_DESC FirstTexDesc; UINT count; MultiTexCT->GetConstantDesc(FirstTexHandle, &FirstTexDesc, &count); . . . // Устанавливаем текстуру/режимы выборки для // объекта выборки FirstTex. Мы определяем этап с которым // связан FirstTex по значению члена данных // D3DXCONSTANT_DESC::RegisterIndex: Device->SetTexture(FirstTexDesc.RegisterIndex, Tex);

Device->SetSamplerState(FirstTexDesc.RegisterIndex, D3DSAMP_MAGFILTER, D3DTEXF_LINEAR); Device->SetSamplerState(FirstTexDesc.RegisterIndex, D3DSAMP_MINFILTER, D3DTEXF_LINEAR); Device->SetSamplerState(FirstTexDesc.RegisterIndex, D3DSAMP_MIPFILTER, D3DTEXF_LINEAR);

ПРИМЕЧАНИЕ

Альтернативным способом, который может применяться вместо использования объектов выборки, является применение более специализированных и строго типизированных типов sampler1D, sampler2D, sampler3D и samplerCube. Эти типы обеспечивают большую безопасность типов и гарантируют, что будут использоваться только соответствующие функции tex*. Например, для объекта sampler2D могут применяться только функции tex2D*. Аналогично, для объекта sampler3D могут применяться только функции tex3D*.

Основы мультитекстурирования


Мультитекстурирование, возможно, является самой простой из техник, которые можно реализовать с помощью пиксельного шейдера. Более того, поскольку пиксельные шейдеры заменяют стадию мультитекстурирования фиксированного конвейера, нам необходимо иметь хотя бы базовое представление о том, что происходит на этапе мультитекстурирования. Данный раздел представляет собой краткий обзор мультитекстурирования.

Когда ранее, в главе6, мы обсуждали наложение текстур, обсуждение мультитекстурирования в фиксированном конвейере функций было пропущено по двум причинам. Во-первых, мультитекстурирование это часть более сложного процесса и мы посчитали, что в тот момент эта тема была бы слишком сложна для восприятия. Во-вторых, фиксированные функции этапа мультитекстурирования заменяются новыми и более мощными пиксельными шейдерами; следовательно имеет смысл не тратить время на устаревшие фиксированные функции этапа мультитекстурирования.

Идея, лежащая в основе мультитекстурирования отчасти связана со смешиванием. В главе 7 мы узнали о смешивании растеризуемых пикселей с пикселями ранее записанными во вторичный буфер для реализации некоторых эффектов. Теперь мы расширим эту идею для нескольких текстур. Итак, мы разрешаем одновременное использование нескольких текстур и затем определяем, как эти текстуры должны быть смешаны одна с другой для получения требуемого эффекта. Наиболее часто мультитекстурирование используется для освещения. Вместо того, чтобы воспользоваться моделью освещения Direct3D на этапе обработки вершин, мы применяем специальные карты текстур, называемые картами освещения (light map), которые сообщают о том, как освещена поверхность. Представим, например, что нам надо осветить прожектором большой ящик. Мы можем описать прожектор в виде структуры D3DLIGHT9, или можно смешать вместе карту текстур, представляющую ящик и карту освещения, представляющую прожектор, как показано на рис 18.1.

Индекс этапа текстурирования/выборки идентифицирует этап текстурирования/выборки для которого мы устанавливаем текстуру или режим выборки. Следовательно, мы можем разрешить использование нескольких текстур и установить для каждой из них требуемые режимы выборки, указывая различные индексы этапов. Ранее в этой книге мы всегда указывали индекс 0, задавая первый этап, поскольку использовали только одну текстуру одновременно. Если, например, нам надо разрешить использование трех текстур, мы используем этапы 0, 1 и 2, как показано ниже:

// Устанавливаем первую текстуру и режимы выборки для нее Device->SetTexture( 0, Tex1); Device->SetSamplerState(0, D3DSAMP_MAGFILTER, D3DTEXF_LINEAR); Device->SetSamplerState(0, D3DSAMP_MINFILTER, D3DTEXF_LINEAR); Device->SetSamplerState(0, D3DSAMP_MIPFILTER, D3DTEXF_LINEAR);

// Устанавливаем вторую текстуру и режимы выборки для нее Device->SetTexture( 1, Tex2); Device->SetSamplerState(1, D3DSAMP_MAGFILTER, D3DTEXF_LINEAR); Device->SetSamplerState(1, D3DSAMP_MINFILTER, D3DTEXF_LINEAR); Device->SetSamplerState(1, D3DSAMP_MIPFILTER, D3DTEXF_LINEAR);

// Устанавливаем третью текстуру и режимы выборки для нее Device->SetTexture( 2, Tex3); Device->SetSamplerState(2, D3DSAMP_MAGFILTER, D3DTEXF_LINEAR); Device->SetSamplerState(2, D3DSAMP_MINFILTER, D3DTEXF_LINEAR); Device->SetSamplerState(2, D3DSAMP_MIPFILTER, D3DTEXF_LINEAR);

Данный код разрешает использование трех текстур, Tex1, Tex2 и Tex3, и устанавливает режимы фильтрации для каждой текстуры.



Создание пиксельного шейдера


18.3.2. Создание пиксельного шейдера

Когда у нас есть скомпилированный код шейдера, мы можем получить указатель на представляющий пиксельный шейдер интерфейс IDirect3DPixelShader9, воспользовавшись следующим методом:

HRESULT IDirect3DDevice9::CreatePixelShader( CONST DWORD *pFunction, IDirect3DPixelShader9** ppShader );

pFunction — Указатель на скомпилированный код шейдера.

ppShader — Возвращает указатель на интерфейс IDirect3DPixelShader9.

Предположим, например, что переменная shader — это экземпляр интерфейса ID3DXBuffer, который содержит скомпилированный код шейдера. Тогда, для получения интерфейса IDirect3DPixelShader9 следует написать:

IDirect3DPixelShader9* MultiTexPS = 0; hr = Device->CreatePixelShader( (DWORD*)shader->GetBufferPointer(), &MultiTexPS);

ПРИМЕЧАНИЕ



Уничтожение пиксельного шейдера



18.3.4. Уничтожение пиксельного шейдера

Как и для всех интерфейсов Direct3D, здесь для очистки мы должны при завершении работы с интерфейсом вызвать его метод Release. Продолжая изучать пример пиксельного шейдера созданного в разделе 18.3.2, мы получаем:

d3d::Release<IDirect3DPixelShader9*>(MultiTexPS);



Установка пиксельного шейдера



18.3.3. Установка пиксельного шейдера

После того, как мы получили указатель на представляющий наш пиксельный шейдер интерфейс IDirect3DPixelShader9, можно разрешить его использование с помощью следующего метода:

HRESULT IDirect3DDevice9::SetPixelShader( IDirect3DPixelShader9* pShader );

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

Device->SetPixelShader(MultiTexPS);



Входные и выходные данные пиксельного шейдера

В вершинный шейдер передаются две вещи: цвет и координаты текстуры. Обе — для пиксела.

ПРИМЕЧАНИЕ

Координаты текстуры для пиксела это просто пара координат (u, v), определяющая тексел текстуры, который будет наложен на данный пиксел. Direct3D перед входом в пиксельный шейдер вычисляет цвет и координаты текстуры для каждого пиксела на основании цветов и координат текстуры вершин. Количество цветов и координат текстур, передаваемых в пиксельный шейдер зависит от того, сколько цветов и координат текстур возвращает вершинный шейдер. Например, если вершинный шейдер возвращает два цвета и три набора координат текстур, то Direct3D вычислит для каждого пикселя два цвета и три набора координат текстуры, которые и передаст в пиксельный шейдер. Мы отображаем переданные цвета и координаты текстур на переменные шейдера с помощью синтаксиса с двоеточием. Для рассматриваемого примера мы могли бы написать:

struct PS_INPUT { vector c0 : COLOR0; vector c1 : COLOR1; float2 t0 : TEXCOORD0; float2 t1 : TEXCOORD1; float2 t2 : TEXCOORD2; };

Возвращает пиксельный шейдер единственное вычисленное значение цвета пикселя:

struct PS_OUTPUT { vector finalPixelColor : COLOR0; };



Визуализация освещенного прожектором



Рисунок 18.1. Визуализация освещенного прожектором ящика с использованием мультитекстурирования. Здесь мы комбинируем две текстуры, перемножая их соответствующие тексели

ПРИМЕЧАНИЕ

Смешивание текстур (в данном примере двух) для освещения ящика имеет два преимущества по сравнению с использованием модели освещения Direct3D:

Освещение заранее вычисляется в карте освещения прожектора. Следовательно, во время выполнения освещение рассчитывать не нужно, что экономит время процессора. Конечно же, заранее можно вычислить освещение только для статических объектов и статических источников света.

Поскольку карты освещения вычисляются заранее, мы можем использовать более точные и сложные модели освещения, чем модель Direct3D. (Чем лучше результат освещения, тем реалистичнее выглядит сцена.)

ПРИМЕЧАНИЕ

ПРИМЕЧАНИЕ












Пример приложения: мультитекстурирование в пиксельном шейдере


Пример приложения для данной главы демонстрирует мультитекстурирование с использованием пиксельных шейдеров. Пример формирует текстурированный квадрат, отмеченный на Рисунок 18.2 как «результат» путем смешивания текстуры ящика, текстуры прожектора и текстуры со строкой «Pixel Shader Sample».



Разрешение работы с несколькими текстурами


18.1.1. Разрешение работы с несколькими текстурами

Вспомните, что текстуры устанавливаются с помощью метода IDirect3DDevice9::SetTexture, а режимы выборки (sampler state) устанавливаются с помощью метода IDirect3DDevice9::SetSamplerState, чьи прототипы выглядят так:

HRESULT IDirect3DDevice9::SetTexture( DWORD Stage, // индекс этапа текстурирования IDirect3DBaseTexture9 *pTexture );

HRESULT IDirect3DDevice9::SetSamplerState( DWORD Sampler, // индекс этапа выборки D3DSAMPLERSTATETYPE Type, DWORD Value );

ПРИМЕЧАНИЕ

Этап выборки с индексом i связвн с i-ым этапом текстурирования. То есть i-ый этап выборки задает режимы выборки для i-ой установленной текстуры. Повторим еще раз, D3DXCompileShaderFromFile — это функция, которая возвращает скомпилированный код шейдера (shader). Вспомните, что цвета пикселов грани получаются путем интерполяции цветов вершин. Как и в примере смешивания из главы 7, получаемое в результате изображение зависит от того, как именно смешиваются текстуры. В фиксированных функциях этапа мультитекстурирования формула смешивания задавалась с помощью относящихся к текстурированию режимов визуализации. В пиксельных шейдерах мы пишем функцию смешивания в коде шейдера в виде простого математического выражения. Второй подход позволяет выполнять смешивание текстур любым желаемым способом. Мы более подробно поговорми о смешивании текстур, когда будем обсуждать в этой главе пример приложения. Этап мультитекстурирования обычно используется для реализации всего механизма освещения статических объектов. Например, у нас может быть карта текстур, хранящая цвета объекта, такая как карта текстур ящика. Затем мы можем создать карту рассеянного освещения, хранящую оттенки рассеиваемого поверхностями света, отдельную карту отраженного света для хранения оттенков отражаемого поверхностями света, карту тумана, определяющую насколько поверхности будут скрыты туманом, и карту деталей, хранящую небольшие, часто встречающиеся на поверхностях детали. Когда все эти текстуры объединяются, мы выполняем освещение окрашивание и добавление деталей для сцены используя только просмотр предварительно созданных текстур. Карта освещения прожектора — это простейший тривиальный пример карты освещения. Обычно используются специальные программы, генерирующие карты освещения на основе сцены и источников света. Генерация карт освещения выходит за рамки этой книги. Интересующихся читателей мы отсылаем к описанию карт освещения в книге Алана Ватта и Фабио Поликарпо «3D Games: Real-time Rendering and Software Technology».