(495) 925-0049, ITShop интернет-магазин 229-0436, Учебный Центр 925-0049
  Главная страница Карта сайта Контакты
Поиск
Вход
Регистрация
Рассылки сайта
 
 
 
 
 

Разработка системы частиц на платформе DirectX 9. Часть II

Источник: habrahabr
A1ex

Этот пост является 2-ой и последней частью статьи о разработки системы частиц на DirectX 9. Если вы еще не читали первую часть, то рекомендую с ней ознакомится.

В этой части статьи будет рассмотрено: работа со спрайтами, вершинные и пиксельные шейдеры, эффекты, пост-эффекты. В частности для реализации пост-эффекта приём рендера в текстуру.

0. Базовые сведения

Спрайты

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

Текстура вместо пикселей, как мы привыкли, имеет тексели (texel). Direct3D использует для текстур систему координат, образованную горизонтальной осью U и вертикальной осью V.

Вершинные шейдеры

Вершинные шейдеры это программа, которая создается на специальном языке HLSL (или ассемблере), и занимается преобразованием вершин и освещением. В вершинном шейдере мы можем взять положение вершины и переместить её в совсем другое место. В статье вершинные шейдер будет так же использоваться для генерации координат текстур.

Пиксельные шейдеры

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

Эффекты и пост-эффекты

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

1. Текстурируем частицы

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

struct VertexData
{
    float x,y,z;
    float u,v; // Храним коориднаты текстуры
};

Значения u и v, необходимо инициализовать нулем при создании.

Так же необходимо изменить флаги при создании буфера, и описание буфера:

device->CreateVertexBuffer(count*sizeof(VertexData), D3DUSAGE_WRITEONLY,
        D3DFVF_XYZ / D3DFVF_TEX0, D3DPOOL_DEFAULT, &pVertexObject, NULL);
// ...

D3DVERTEXELEMENT9 decl[] =
    {
        { 0, 0, D3DDECLTYPE_FLOAT3, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_POSITION, 0 },
        { 0, 12, D3DDECLTYPE_FLOAT2, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_TEXCOORD, 0 },
        D3DDECL_END()
    };

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

А теперь осталось загрузить текстуру и изменить состояния рендера:


float pointSize = 5; // Размер частиц в единицах пространства вида
    device->SetRenderState(D3DRS_POINTSIZE_MAX, *((DWORD*)&pointSize));
    device->SetRenderState(D3DRS_POINTSIZE, *((DWORD*)&pointSize));
    device->SetRenderState(D3DRS_LIGHTING,FALSE);
    device->SetRenderState(D3DRS_POINTSPRITEENABLE, TRUE ); //Включаем рисование спрайтов поверх точек
    device->SetTextureStageState(0, D3DTSS_ALPHAARG1, D3DTA_TEXTURE);
    device->SetTextureStageState(0, D3DTSS_ALPHAOP, D3DTOP_SELECTARG1);
    device->SetRenderState(D3DRS_ALPHABLENDENABLE, TRUE);
    device->SetRenderState(D3DRS_SRCBLEND, D3DBLEND_SRCALPHA);
    device->SetRenderState(D3DRS_DESTBLEND, D3DBLEND_INVSRCALPHA);
    device->SetRenderState(D3DRS_ZENABLE, FALSE);

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

IDirect3DTexture9 *particleTexture = NULL,
D3DXCreateTextureFromFile(device, L"particle.png", &particleTexture); //Создаем текстуру
device->SetTexture(0, particleTexture); //Устанавливаем текстуру

Загружаем текстуру и устанавилваем из файла, которая будет представлять частицу. 

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

Результат визуализации:

2. Эффекты

Для разработки эффектов существует замечательная программа от NVIDIA, называется она Fx Composer. Поддерживается отладка шейдеров, шейдеры 4-ой версии, DIrect3D (9, 10) и OpenGL. Очень рекомендую, но в данной статье эта среда разработки рассматриваться не будет.

Для начала рассмотрим основную структуру эффектов:

Скрытый текст
float4x4 WorldViewProj; // Входной параметр. Матрица 4x4

//Входной параметр текстура
texture Base  <
    string UIName =  "Base Texture";
    string ResourceType = "2D";
>;

//Сэмплер, используется для выборки текселей
sampler2D BaseTexture = sampler_state {
    Texture = <Base>;
    AddressU = Wrap;
    AddressV = Wrap;
};

//Структура, описывающая входные параметры для вершинного шейдера
struct VS_INPUT 
{
    float4 Position : POSITION0;
    float2 Tex      : TEXCOORD0;

};

//Структура для выходных параметров
struct VS_OUTPUT 
{
    float4 Position : POSITION0;
    float2 Tex      : TEXCOORD0;

};

// Вершинный шейдер
VS_OUTPUT mainVS(VS_INPUT Input)
{
    VS_OUTPUT Output;

    Output.Position = mul( Input.Position, WorldViewProj );
    Output.Tex = Input.Tex;

    return( Output );
}

// Пиксельный шейдер
float4 mainPS(float2 tex: TEXCOORD0) : COLOR
{
    return tex2D(BaseTexture, tex);
}

// Описание "Техники"
technique technique0 
{		
    //Описание прохода визуализации
    pass p0 
    { 
        CullMode = None; // Устанавливаем состояние рендера
        // Выолняем
        VertexShader = compile vs_2_0 mainVS(); // вершинный шейдер
        PixelShader = compile ps_2_0 mainPS(); //  пиксельный шейдер
    }
}

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

Настало время написать свой простой эффект, который, например будет окрашивать частицы в красный цвет:

Скрытый текст
float4x4 WorldViewProj; // Входной параметр. Матрица 4x4

//Входной параметр текстура (спрайт)
texture Base  <
    string UIName =  "Base Texture";
    string ResourceType = "2D";
>;

//Сэмплер, используется для выборки текселей
sampler2D BaseTexture = sampler_state {
    Texture = <Base>;
    AddressU = Wrap;
    AddressV = Wrap;
};

//Структура, описывающая входные параметры для вершинного шейдера
struct VS_INPUT 
{
    float4 Position : POSITION0;
    float2 Tex      : TEXCOORD0;

};

//Структура для выходных параметров
struct VS_OUTPUT 
{
    float4 Position : POSITION0;
    float2 Tex      : TEXCOORD0;

};

// Вершинный шейдер
VS_OUTPUT mainVS(VS_INPUT Input)
{
    VS_OUTPUT Output;

    Output.Position = mul( Input.Position, WorldViewProj ); // Преобразуем координаты вершин в пространство вида
    Output.Tex = Input.Tex; // Координаты текстуры мы не будем модифицировать

    return( Output );
}

// Пиксельный шейдер
float4 mainPS(float2 tex: TEXCOORD0) : COLOR
{
    return tex2D(BaseTexture, tex) * float4(1.0, 0, 0, 1.0); // Смешиваем цвет текстуры с красным
}

// Описание "Техники"
technique technique0 
{		
    //Описание прохода визуализации
    pass p0 
    { 
        CullMode = None; // Устанавливаем состояние рендера
        // Выолняем
        VertexShader = compile vs_2_0 mainVS(); // вершинный шейдер
        PixelShader = compile ps_2_0 mainPS(); //  пиксельный шейдер
    }
}

Код этого эффекта мало отличается от базовой структуры, ранее рассмотренной нами. Мы добавили лишь смешивание с красным цветом методом умножение (Multiply Blend). Вот что у нас получилось:

Неплохо, но можно изменить режим наложения на другой, и сделать смешивание не с одним цветом, а с целой текстурой.
Для того, чтобы у нас получилось правильно смешать визуализацию частиц и текстуру, нам необходимо воспользоваться приемом, который называется Render Target (цель визуализации). Суть приема проста, мы визуализируем нашу сцену в текстуру, а потом уже накладываем эффекты на уже растрированное изображение.

Вот полный код эффекта реализующего это:

Скрытый текст
float4x4 WorldViewProj;

texture Base  <
    string UIName =  "Base Texture";
    string ResourceType = "2D";
>;

sampler2D BaseTexture = sampler_state {
    Texture = <Base>;
    AddressU = Wrap;
    AddressV = Wrap;
};

texture Overlay  <
    string UIName =  "Overlay Texture";
    string ResourceType = "2D";
>;

sampler2D OverlayTexture = sampler_state {
    Texture = <Overlay>;
    AddressU = Wrap;
    AddressV = Wrap;
};

// Текстура, которая будет использоваться для рендера
texture PreRender : RENDERCOLORTARGET
    <
    string Format = "X8R8G8B8" ;
    >;

// И сэмплер для неё
sampler2D PreRenderSampler = sampler_state {
    Texture = <PreRender>;
};

struct VS_INPUT 
{
    float4 Position : POSITION0;
    float2 Tex      : TEXCOORD0;

};

struct VS_OUTPUT 
{
    float4 Position : POSITION0;
    float2 Tex      : TEXCOORD0;

};

VS_OUTPUT cap_mainVS(VS_INPUT Input)
{
    VS_OUTPUT Output;

    Output.Position = mul( Input.Position, WorldViewProj );
    Output.Tex = Input.Tex;

    return( Output );
}

float4 cap_mainPS(float2 tex: TEXCOORD0) : COLOR
{
    return tex2D(BaseTexture, tex);
}

///////////////////////////////////////////////////////

struct Overlay_VS_INPUT 
{
    float4 Position : POSITION0;
    float2 Texture1 : TEXCOORD0;

};

struct Overlay_VS_OUTPUT 
{
    float4 Position : POSITION0;
    float2 Texture1 : TEXCOORD0;
    float2 Texture2 : TEXCOORD1;

};

vector blend(vector bottom, vector top)
{
    //Linear light
    float r = (top.r < 0.5)? (bottom.r + 2*top.r - 1) : (bottom.r + top.r);
    float g = (top.g < 0.5)? (bottom.g + 2*top.g - 1) : (bottom.g + top.g);
    float b = (top.b < 0.5)? (bottom.b + 2*top.b - 1) : (bottom.b + top.b);

    return  vector(r,g,b,bottom.a);
}

Overlay_VS_OUTPUT over_mainVS(Overlay_VS_INPUT Input)
{
    Overlay_VS_OUTPUT Output;

    Output.Position = mul( Input.Position, WorldViewProj );
    Output.Texture1 = Input.Texture1;
    Output.Texture2 = Output.Position.xy*float2(0.5,0.5) + float2(0.5,0.5); // преобразуем координаты вершины, в координаты текстуры

    return( Output );
}

float4 over_mainPS(float2 tex :TEXCOORD0, float2 pos :TEXCOORD1) : COLOR 
{
    return blend(tex2D(OverlayTexture, pos), tex2D(PreRenderSampler, tex));
}


technique technique0 
{		
    pass p0 
    {
        CullMode = None;
        VertexShader = compile vs_2_0 cap_mainVS();
        PixelShader = compile ps_2_0 cap_mainPS();
    }

    pass p1 
    {
        CullMode = None;
        VertexShader = compile vs_2_0 over_mainVS();
        PixelShader = compile ps_2_0 over_mainPS();
    }
}

Как вы заметили, появился еще один этап визуализации. На первом этапе мы визуализируем частицы такими, какие они есть. Причем визуализацию мы должны будем выполнить в текстуру. А уже во втором проходе визуализации мы накладываем на изображение другую текстуру используя смешивание Linear Light.

Использование эффектов в программе

Эффекты мы создали, настало время изменить код, добавив использование эффектов.
Нам необходимо создать и скомпилировать код эффектов, загрузить дополнительную текстуру, а так же создать текстуру, в которую мы будем выполнять визуализацию.

Скрытый текст
ID3DXBuffer* errorBuffer = 0;
D3DXCreateEffectFromFile( // Создаем и компилируем эффект
    device, 
    L"effect.fx", 
    NULL,
    NULL,
    D3DXSHADER_USE_LEGACY_D3DX9_31_DLL, //Используем компилятор для DirectX 9
    NULL,
    &effect, 
    &errorBuffer );

if( errorBuffer ) //Выводим ошибки, если они есть
{
    MessageBoxA(hMainWnd, (char*)errorBuffer->GetBufferPointer(), 0, 0);
    errorBuffer->Release();
    terminate();
}

// Создаем матрицу, которую передадим в качестве WorldViewProj
// Она необходима для работы вершинного шейдера
D3DXMATRIX W, V, P, Result; 
D3DXMatrixIdentity(&Result);
device->GetTransform(D3DTS_WORLD, &W);
device->GetTransform(D3DTS_VIEW, &V);
device->GetTransform(D3DTS_PROJECTION, &P);
D3DXMatrixMultiply(&Result, &W, &V);
D3DXMatrixMultiply(&Result, &Result, &P);

effect->SetMatrix(effect->GetParameterByName(0, "WorldViewProj"), &Result);

// Выбираем самую первую технику
effect->SetTechnique( effect->GetTechnique(0) );

IDirect3DTexture9 *renderTexture = NULL,
    *overlayTexture = NULL;

// Поверхности будут использованы для установки цели визуализации
IDirect3DSurface9* orig =NULL
    , *renderTarget = NULL;

D3DXCreateTextureFromFile(device, L"overlay.png", &overlayTexture);

// Создаем текстуру, в которую будет выполняться визуализация
D3DXCreateTexture(device, Width, Height, 0, D3DUSAGE_RENDERTARGET, D3DFMT_X8B8G8R8, D3DPOOL_DEFAULT, &renderTexture);
// Сохраняем поверхность, для рендера в текстуру
renderTexture->GetSurfaceLevel(0, &renderTarget); 
// Сохраняем оригинальную поверхность
device->GetRenderTarget(0, &orig);

// Устанавлим текстуры эффекта
auto hr = effect->SetTexture( effect->GetParameterByName(NULL, "Overlay"), overlayTexture);
hr /= effect->SetTexture( effect->GetParameterByName(NULL, "Base"), particleTexture);
hr /= effect->SetTexture( effect->GetParameterByName(NULL, "PreRender"), renderTexture);

if(hr != 0)
{
    MessageBox(hMainWnd, L"Unable to set effect textures.", L"", MB_ICONHAND);
}

Как мы видим, эффект перед использованием необходимо скомпилировать, выбрать технику, а так же установить все используемые им данные.
Для визуализации в текстуру нам необходимо создать саму текстуру, размерами с оригинальную сцену, и поверхность для нее. Поверхность будет использована при визуализации.

Теперь осталось только отрисовать текстуры с использованием эффекта. Делается это так:

Скрытый текст
UINT passes = 0; // Здесь будет хранится количество этапов визуализации
effect->Begin(&passes, 0);
for(UINT i=0; i<passes; ++i)
{
    effect->BeginPass(i);

    if(i == 0)
    {
        // Очищаем экранный буфер
        device->Clear( 0, NULL, D3DCLEAR_TARGET, D3DCOLOR_XRGB(0,0,0), 1.0f, 0 );
        // Устанавливаем текстуру, а точнее её поверхность, в качестве цели визуализации
        device->SetRenderTarget(0, renderTarget);
        // Очищаем текстуру, в которую будет произведен рендер
        device->Clear(0, NULL, D3DCLEAR_TARGET, D3DCOLOR_XRGB(0,0,0), 1.0f, 0);
        // Рисуем частицы
        DrawParticles();
    }
    else if(i == 1)
    {
        // Востанавливаем оригинальную поверхность
        device->SetRenderTarget(0, orig);
        // Рисуем прямоугольник, с наложенной на него текстурой (RenderTexture)
        DrawRect();
    }

    effect->EndPass();
}
effect->End();

// Выводим частицы на экран
device->Present(NULL, NULL, NULL, NULL);

В коде мы использовали DrawRect(), эта функция рисует прямоугольник, на которые наложена текстураRenderTexture. Это особенность приема, после визуализации в текстуру, нам необходимо как-то вывести её на экран для последующей обработки. В этом нам и помогает прямоугольник, который мы рисуем так, чтобы он занимал все экранное пространство. Код инициализации вершин и визуализации прямоугольника я приводить не буду, чтобы не раздувать статью еще больше. Скажу только, что все необходимые действия аналогичны тем, что мы проводили при инициализации частиц. Если у вас возникли трудности, то вы можете посмотреть как реализована эта функция в коде примера.

Эффекты используется так: сначала мы вызываем метод Begin(), получая количество проходов визуализации в эффекте. Затем перед каждым проходом вызываем BeginPass(i), а после EndPass(). И наконец после окончания визуализации мы вызываем метод End().

Вот что у нас получилось:

На этом статья заканчивается, всем спасибо за внимание. Буду рад ответить на возникшие у вас вопросы в комментариях.
Полный исходный код проекта доступен на GitHub. Внимание, для запуска скомпилированного примера необходимо установить VisualC++ Redistributable 2012

UPD
Для тех, кто считает, что D3D9 безнадежно устарел, или тем, кому просто хочется, чтобы все расчеты производились на GPU - имеется еще один пример, только уже на D3D10. Как обычно пример и скомпилированное демо доступны на GitHub. Расчеты на GPU прилагаются :)

Ссылки по теме


 Распечатать »
 Правила публикации »
  Написать редактору 
 Рекомендовать » Дата публикации: 07.11.2012 
 

Магазин программного обеспечения   WWW.ITSHOP.RU
Allround Automation PL/SQL Developer - Annual Service Contract - Unlimited
Toad Data Modeler Per Seat License/Maint
Stimulsoft Reports.Ultimate Single License Includes one year subscription
Allround Automation Direct Oracle Access Standard license
JIRA Software Commercial (Cloud) Standard 10 Users
 
Другие предложения...
 
Курсы обучения   WWW.ITSHOP.RU
 
Другие предложения...
 
Магазин сертификационных экзаменов   WWW.ITSHOP.RU
 
Другие предложения...
 
3D Принтеры | 3D Печать   WWW.ITSHOP.RU
 
Другие предложения...
 
Новости по теме
 
Рассылки Subscribe.ru
Информационные технологии: CASE, RAD, ERP, OLAP
Безопасность компьютерных сетей и защита информации
Новости ITShop.ru - ПО, книги, документация, курсы обучения
Программирование в AutoCAD
Компьютерный дизайн - Все графические редакторы
Adobe Photoshop: алхимия дизайна
Работа в Windows и новости компании Microsoft
 
Статьи по теме
 
Новинки каталога Download
 
Исходники
 
Документация
 
 



    
rambler's top100 Rambler's Top100