Post process. Общее

Во времена начала программирования графики я очень часто не мог понять что такое post process (пост процесс), казалось бы, это то что идет после самого процесса рисования графики) Но это понятие содержит в себе очень много смысла, и оно достаточно простое для понимания)

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

Пример (лучше смотреть в полном размере):

Слева без SSAO и AA пост процессов, справа с SSAO и AA
Слева без SSAO и AA пост процессов, справа с SSAO и AA

Терминология

Post process (жаргон: пост процесс)- метод обработки/изменения изображения, после его рендера.

То есть post process, как правило, следует после процесса рендера сцены.

Screen space (жаргон: скрин спэйс) — пространство экрана. Говоря о screen space обычно подразумевается работа только с видимой частью сцены либо работа с данными видимой части сцены, расчеты которых производятся исходя из данных экрана (к примеру восстановление координат пикселя из карты глубины или расчет освещения в deferred shading).

Full screen (жаргон: фулл скрин) — полный экран, размеры аналогичные размерам экрана.

Full screen quad (жаргон: фулл скрин квад, полноэкранный квад)- прямоугольник рисуемый на весь экран, то есть этот прямоугольник имеет размеры экрана. Состоит из двух треугольников.

Текстура (render target) натягиваемая на full screen quad для некоторых эффектов может иметь больший либо меньший размер, но сам прямоугольник имеет всегда константные размеры, которые равны размерам экрана.

Проход (pass) — отсриcоdка full screen quad (с использованием текстур, если надо, и применением пиксельного шейдера).

Post process effect (жаргон: пост процессорный эффект, иногда просто эффект, однако легко перепутать с эффектом частиц) — результат работы всех составляющих частей одного прохода (одного эффекта, возможно в несколько проходов) пост процесса, проявляющийся в изменении исходного изображения.

Средства

Для пост процесса необходима исходная текстура, которая получается после прохода рендера сцены.

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

Получение текстуры при прямом рендере в back buffer зависит от GAPI. Для dx9 я не нашел элегантного решения, которое бы удовлетворяло бы и в плане производительности и простоты. В движке мы используем deferred shading) Ссылка для поиска метода back buffer -> texture на d3d9.

Для многих эффектов необходима карта глубины. Ее получение при прямом рендере в back buffer настолько же магично как и получение текстуры back buffer (возможно я преувеличиваю, так как серьезно не изучал данный вопрос, а сразу же перешел к deferred shading).

Получение depth map с одной стороны может показаться сложным процесс, но это не так. Ссылка где можно ознакомится с методом. Ссылка где я нашел этот метод. Это все работает, сам проверял, и даже есть прирост производительности (очень субъективно +5%). там же приводится код, но на всякий случай я его продублирую:

//объявляем флаг формата текстуры
#define FOURCC_INTZ ((D3DFORMAT)(MAKEFOURCC(‘I’,’N’,’T’,’Z’)))

...
//проверка поддержки формата (наверное железо без поддержки уже не встретить)
HRESULT hr = pd3d->CheckDeviceFormat(AdapterOrdinal, DeviceType, AdapterFormat, D3DUSAGE_DEPTHSTENCIL, D3DRTYPE_TEXTURE, FOURCC_INTZ);

BOOL bINTZDepthStencilTexturesSupported = (hr == D3D_OK);

...

//создание INTZ depth stencil текстуры
IDirect3DTexture9 *pINTZDST;
pd3dDevice->CreateTexture(dwWidth, dwHeight, 1, D3DUSAGE_DEPTHSTENCIL, FOURCC_INTZ, D3DPOOL_DEFAULT, &pINTZDST, NULL);

//получение surface текстуры глубины
IDirect3DSurface9 *pINTZDSTSurface;
pINTZDST->GetSurfaceLevel(0, &pINTZDSTSurface);

//установка этого surface в качестве глубины
pd3dDevice->SetDepthStencilSurface(pINTZDSTSurface);

...

//после рендера сцены pINTZDST будет содержать глубину (гиперболическую)
pd3dDevice->SetTexture(0, pINTZDST);

Необходим сам full screen quad. Ниже приведен пример создания full screen quad из нашего движка SkyXEngine 0.9.3 с поддержкой DirectX 9:

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

D3DXCreateMesh(2, 4, D3DXMESH_MANAGED, layoutquad, DXDevice, &ScreenTexture);

struct  VERTEX_SCREEN_TEXTURE { float x, y, z, tx, ty, tz; };

const float offset_pixel_x = 1.0f / float(D3DAPP.BackBufferWidth);
const float offset_pixel_y = 1.0f / float(D3DAPP.BackBufferHeight);

VERTEX_SCREEN_TEXTURE AddVertices[] =
{
	{ -1.0f - offset_pixel_x, -1.0f + offset_pixel_y, 1.0f, 0.0f, 1.0f, 0 },
	{ -1.0f - offset_pixel_x, 1.0f + offset_pixel_y, 1.0f, 0.0f, 0.0f, 1 },
	{ 1.0f - offset_pixel_x, 1.0f + offset_pixel_y, 1.0f, 1.0f, 0.0f, 2 },
	{ 1.0f - offset_pixel_x, -1.0f + offset_pixel_y, 1.0f, 1.0f, 1.0f, 3 },
};

void* Vertices;
if (!FAILED(ScreenTexture->LockVertexBuffer(0, (void**)&Vertices)))
{
	memcpy(Vertices, AddVertices, sizeof(AddVertices));
	ScreenTexture->UnlockVertexBuffer();
}

WORD* i = 0;
ScreenTexture->LockIndexBuffer(0, (void**)&i);
i[0] = 0; i[1] = 1; i[2] = 2;
i[3] = 0; i[4] = 2; i[5] = 3;
ScreenTexture->UnlockIndexBuffer();

Размеры full screen quad задаются в коэфициентах (типа проценты только от 0 до 1).

Следует обратить внимание на:

const float offset_pixel_x = 1.0f / float(D3DAPP.BackBufferWidth);
const float offset_pixel_y = 1.0f / float(D3DAPP.BackBufferHeight);

Здесь происходит вычисление небольшого смещения для позиции full screen quad, чтобы не было размытия.

Далее требуются шейдеры (наверное поддержка шейдеров ниже версии 3.0 будет неактуальна, потому что 3.0 это еще DirectX 9).

В большинстве случаев, вершинный шейдер тривиален:


struct vs_in_pp
{
	half4 Position	:POSITION;
	half2 TexUV	:TEXCOORD0;
};

vs_out_pp main(vs_in_pp IN)
{
	vs_out_pp OUT;
	OUT.Position = IN.Position;
	OUT.TexUV = IN.TexUV;
	return OUT;
}

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

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

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

Процесс

В общем процесс прохода выглядит следующим образом:

  1. Установка целевой текстуры получателя (это не исходная текстура!!!) в качестве render target.
  2. Передача всех необходимых текстур и исходной текстуры в том числе (если надо).
  3. Установка шейдеров
  4. Отрисовка full screen quad
  5. Очистка регистров текстур и шейдеров (опционально, но во избежание ошибок рекомендую, сам спотыкался не раз).

Следует заметить что целевая текстура не может быть исходной текстурой (в DirectX 9 точно, это вызовет undefined behavior).

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

Post process эффекты

В post process может быть множество эффектов:

  • туман
  • черно-белое изображение
  • сепия
  • цветовая коррекция
  • lens flare
  • SSAO
  • SSR
  • depth of field
  • morion blur
  • God rays (sun shafts)
  • антиальясинг

Возможности screen space

В пространстве экрана можно многое посчитать:

  • восстановление координат пикселя
  • расчет освещения в deferred shading
  • генерация и сглаживание теней (на основе shadow mapping)
  • генерация bump map
  • объединение слоев полупрозрачной геометрии

У некоторых возможностей по несколько проходов.


Вообще post process может изменить исходное изображение до неузнаваемости, может сделать его намного лучше. А расчеты в screen space могут помочь выиграть в производительности.

В целом сами понятия используемые в post process не сложные, даже порой чрезмерно простые)

Поделиться:

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *

*

*

code