Тут вчера вернулся к отложенному рендеру, довел до ума упаковку сталкерского g-buffer на R4. Итого пакуем все в две DXGI_FORMAT_R8G8B8A8_UNORM и получаем хороший профит. Всего 64 бита, лучше вряд ли сделать. В ваниле была упаковка в 160 бит на R2 и в 96 бит на R4.
Глубину тащим из буфера глубины и восстанавливаем из нее позицию вида через обратную матрицу проекции. Можно другим способом, хотя ванильный вариант восстановления позиции из линейной глубины (через параметры экрана) оказался малость косячным, не совсем корректно восстанавливает...
Вот рецепт:
В первую DXGI_FORMAT_R8G8B8A8_UNORM пишем в три канала диффуз и в альфа-канал хеми. Во вторую пишем в два канала кодированную нормаль, в третий канал материал и в альфа-канал спекуляр. Важно делать именно так, тогда при освещении лайтами будет читаться только одна текстура со всеми необходимыми для освещения данными (в ваниле спекуляр писался вместе с диффузом).
Изюминка всего - упаковка нормали в сферические координаты. Пакует качественно, хоть и расходует чуть больше ALU (на современных GPU это почти бесплатно). В игре проверено, все четко и все летает.
Паковка нормали (при записи дополнительно переводим в диапазон 0..1, а при извлечении из g-buffer обратно в -1..1).
Код
float2 PackNormal(float3 N)
{
float2 res;
res.x = atan2(N.y, N.x) / 3.14159f;
res.y = N.z;
return res;
}
float3 UnpackNormal(float2 res)
{
float2 theta;
sincos(res.x * 3.14159f, theta.x, theta.y);
float2 phi = float2(sqrt(1.0 - res.y * res.y), res.y);
return float3(theta.y * phi.x, theta.x * phi.x, phi.y);
}
Ф-ция восстановления позиции:
Код
float3 GetPositionView(float2 tc, float zdepth)
{
float4 P = 1.f;
P.x = tc.x * 2.f - 1.f;
P.y = -(tc.y * 2.f - 1.f);
P.z = zdepth;
P = mul(m_invP, P);
return P.xyz / P.w;
}
Обратную матрицу проекции создаем в биндере так:
Код
class cl_inv_p : public R_constant_setup {
Fmatrix result;
virtual void setup (R_constant* C) {
D3DXMatrixInverse((D3DXMATRIX*)&result, 0, (D3DXMATRIX*)&RCache.xforms.m_p);
RCache.set_c (C, result);
}
}; static cl_inv_p binder_inv_p;
r_Constant ("m_invP", &binder_inv_p);
И еще. Поскольку мы будем читать из буфера глубины, а он у нас часто установлен активным для трафарета, то нужно создать еще один DepthStencilView с флагом D3D11_DSV_READ_ONLY_DEPTH | D3D11_DSV_READ_ONLY_STENCIL с привязкой к базовому ZB ShaderResourceView, и на фазе combine_1 и при освещении лайтами устанавливать активным его (тогда мы сможем читать из него глубину, даже когда он активный, но при этом будет отключена запись).